mirror of
https://github.com/l0ng-ai/tty7.git
synced 2026-10-03 16:01:59 +00:00
The hosted Mintlify build fails to parse any page with a JS comment (line or block) inside an export body and serves a parse-error placeholder instead; mint dev and mint validate accept the same page. Confirmed with diagnostic pages: the prefix with the sidebar's // comment fails, the same prefix without it renders, and turning it into /* */ still fails. Move both comments to top-level MDX comments, note the rule in the file, undo the earlier % escaping (that guess was wrong), and remove the diagnostic pages.
598 lines
28 KiB
Plaintext
598 lines
28 KiB
Plaintext
---
|
||
title: "tty7"
|
||
description: "A terminal workbench: persistent sessions, remote work, agents."
|
||
mode: "custom"
|
||
---
|
||
|
||
{/* Mirrors CLIAgent::accent_rgb / icon_path: a solid brand disc with the
|
||
agent's own mark masked in white on top, as tab_strip.rs avatar_disc draws it. */}
|
||
export const AGENTS = {
|
||
claude: { name: "Claude Code", c: "#D97757" },
|
||
codex: { name: "Codex", c: "#000000", edge: true },
|
||
gemini: { name: "Gemini", c: "#4285F4" },
|
||
cursor: { name: "Cursor", c: "#9AA0A6" },
|
||
opencode: { name: "OpenCode", c: "#6E56CF" },
|
||
copilot: { name: "Copilot", c: "#8957E5" },
|
||
};
|
||
|
||
export const GROUPS = [
|
||
{ name: "atlas-api", meta: "main", items: [
|
||
{ a: "claude", t: "Spin up a staging instance", s: "working" },
|
||
{ a: "codex", t: "Inspect CLI error logs", s: "working" },
|
||
] },
|
||
{ name: "northwind", meta: "", items: [
|
||
{ a: "cursor", t: "Refactor session auth", b: "feat/session-auth", s: "waiting" },
|
||
{ a: "gemini", t: "Fix pricing fetch timeout", b: "fix/pricing-fetch-timeout", s: "working" },
|
||
] },
|
||
{ name: "orbit", meta: "main · +104 −12", items: [
|
||
{ shell: true, t: "~/code/orbit", on: true },
|
||
{ a: "claude", t: "Optimize route planner" },
|
||
] },
|
||
{ name: "lumen-web", meta: "main · +259 −191", items: [
|
||
{ a: "copilot", t: "Backtest search ranking" },
|
||
] },
|
||
];
|
||
|
||
export const Glyph = ({ a, shell }) => shell
|
||
? <span className="t7w-glyph is-shell">{">_"}</span>
|
||
: <span className={"t7w-glyph" + (AGENTS[a].edge ? " is-edge" : "")} style={{ background: AGENTS[a].c }} title={AGENTS[a].name}>
|
||
<span className="t7w-mark" style={{ WebkitMaskImage: "url(/images/agents/" + a + ".svg)", maskImage: "url(/images/agents/" + a + ".svg)" }} />
|
||
</span>;
|
||
|
||
export const Row = ({ it }) => (
|
||
<div className={"t7w-row" + (it.b ? " is-tall" : "") + (it.on ? " is-on" : "")}>
|
||
<Glyph a={it.a} shell={it.shell} />
|
||
<div className="t7w-rtext">
|
||
<span>{it.t}</span>
|
||
{it.b ? <span className="t7w-branch">{it.b}</span> : null}
|
||
</div>
|
||
{it.s ? <span className={"t7w-dot is-" + it.s} /> : null}
|
||
</div>
|
||
);
|
||
|
||
export const Group = ({ g }) => (
|
||
<div className="t7w-group">
|
||
<div className="t7w-ghead"><b>{g.name}</b><span>{g.meta}</span></div>
|
||
{g.items.map((it) => <Row key={it.t} it={it} />)}
|
||
</div>
|
||
);
|
||
|
||
export const Search = () => (
|
||
<svg width="11" height="11" viewBox="0 0 12 12" style={{ flexShrink: 0 }}>
|
||
<circle cx="5" cy="5" r="3.9" fill="none" stroke="var(--w-a35)" strokeWidth="1.2" />
|
||
<path d="M8 8l2.8 2.8" stroke="var(--w-a35)" strokeWidth="1.2" strokeLinecap="round" />
|
||
</svg>
|
||
);
|
||
|
||
export const Check = () => (
|
||
<svg width="12" height="12" viewBox="0 0 12 12"><path d="M2.5 6.2l2.3 2.3 4.7-5" fill="none" stroke="var(--w-green)" strokeWidth="1.4" strokeLinecap="round" strokeLinejoin="round" /></svg>
|
||
);
|
||
|
||
export const Cross = () => (
|
||
<svg width="12" height="12" viewBox="0 0 12 12"><path d="M3.5 3.5l5 5M8.5 3.5l-5 5" fill="none" stroke="var(--w-red)" strokeWidth="1.4" strokeLinecap="round" /></svg>
|
||
);
|
||
|
||
export const State = ({ s }) => (
|
||
<span className="t7w-ck">{s === "ok" ? <Check /> : s === "fail" ? <Cross /> : <span className="t7w-spin" />}</span>
|
||
);
|
||
|
||
export const CHECKS = [["lint", "ok", "18s"], ["typecheck", "ok", "41s"], ["test (py3.12)", "run", "2m"], ["build image", "ok", "1m 12s"]];
|
||
export const PRS = [
|
||
["Add export endpoints", "#214 · Merged 1d ago", "ok"],
|
||
["Refactor session auth", "#228 · feat/session-auth · Draft", "run"],
|
||
["Fix pricing fetch timeout", "#229 · 1 check failing", "fail"],
|
||
];
|
||
|
||
export const Inspector = () => (
|
||
<aside className="t7w-ins">
|
||
<div className="t7w-bar"><span>Info</span><span>Files</span><span>Changes</span><span className="is-on">PR</span></div>
|
||
<div className="t7w-pr">
|
||
<div className="t7w-prhead">
|
||
<div className="t7w-meta"><span>#231</span><span>·</span><span>Open</span><span className="grow" /><span>12m ago</span></div>
|
||
<div className="t7w-prtitle">Handle empty daily summaries</div>
|
||
<div className="t7w-prbranch"><b>fix/report-daily-summary</b> → main</div>
|
||
<div className="t7w-practs"><span className="t7w-merge">Squash and merge</span><span className="note">Waiting on 1 check</span></div>
|
||
</div>
|
||
<div className="t7w-sect">
|
||
<div className="t7w-shead"><b>Checks</b><span>3 of 4 passed</span></div>
|
||
{CHECKS.map(([n, s, t]) => (
|
||
<div className="t7w-item" key={n}><State s={s} /><span className="grow">{n}</span><span className="dim">{t}</span></div>
|
||
))}
|
||
</div>
|
||
<div className="t7w-sect">
|
||
<div className="t7w-shead"><b>Reviews</b></div>
|
||
<div className="t7w-item"><span className="t7w-avatar">M</span><span className="grow">mara</span><span className="dim" style={{ color: "var(--w-green)" }}>Approved</span></div>
|
||
<div className="t7w-item"><span className="t7w-avatar">J</span><span className="grow">jonas</span><span className="dim">Requested</span></div>
|
||
</div>
|
||
<div className="t7w-sect">
|
||
<div className="t7w-shead"><b>Pull requests</b></div>
|
||
{PRS.map(([t, m, s]) => (
|
||
<div className="t7w-pritem" key={t}><State s={s} /><div><span className="t">{t}</span><span className="m">{m}</span></div></div>
|
||
))}
|
||
</div>
|
||
</div>
|
||
</aside>
|
||
);
|
||
|
||
{/* The hero demo. Five chapters after the promo storyboard
|
||
(videos/tty7-promo/STORYBOARD.md), each a pure function of the seconds t
|
||
since it began, so any frame can be drawn directly and a click can jump
|
||
to any chapter. */}
|
||
|
||
export const CHAPTERS = [
|
||
{ key: "agents", label: "Every agent", d: 7.5 },
|
||
{ key: "cli", label: "Agents drive it", d: 9.5 },
|
||
{ key: "prompt", label: "Editor prompt", d: 7.5 },
|
||
{ key: "search", label: "⌘P everything", d: 8 },
|
||
{ key: "persist", label: "Quit, keep running", d: 7.5 },
|
||
];
|
||
|
||
export const typed = (s, t, t0, cps = 22) => (t < t0 ? "" : s.slice(0, Math.floor((t - t0) * cps)));
|
||
export const typingDone = (s, t, t0, cps = 22) => t >= t0 + s.length / cps;
|
||
export const hl = (s, q) => {
|
||
const i = q ? s.toLowerCase().indexOf(q) : -1;
|
||
return i < 0 ? s : <>{s.slice(0, i)}<mark>{s.slice(i, i + q.length)}</mark>{s.slice(i + q.length)}</>;
|
||
};
|
||
|
||
export const P = ({ br = "main", caret, children }) => (
|
||
<div className="t7w-prompt">
|
||
<span className="p-arrow">→</span><span className="p-dir">orbit</span><span className="p-br">{br}</span><span className="p-dirty">●</span>
|
||
<span className="p-cmd">{children}{caret ? <span className="t7w-caret" /> : null}</span>
|
||
</div>
|
||
);
|
||
|
||
export const O = ({ c = "", children }) => <div className={"t7w-out " + c}>{children}</div>;
|
||
|
||
{/* Sidebar for a frame: the base groups plus whatever the chapter changed. A split
|
||
stays in the same tab; the tab picks up the agent's status. */}
|
||
export const sidebar = (ch, t) => {
|
||
const g = GROUPS.map((x) => ({ ...x, items: x.items.map((it) => ({ ...it })) }));
|
||
const find = (title) => g.flatMap((x) => x.items).find((it) => it.t === title);
|
||
if (ch === 0) find("Refactor session auth").s = t < 3.1 ? "working" : "waiting";
|
||
if (ch === 1 && t >= 3.2) find("~/code/orbit").s = t < 6.4 ? "working" : "done";
|
||
if ((ch === 3 && t >= 4.1) || ch === 4) {
|
||
find("~/code/orbit").on = false;
|
||
g[0].items.unshift({ a: "claude", t: "Handle empty daily summaries", s: ch === 4 && t >= 5.2 ? "done" : "working", on: true });
|
||
}
|
||
return g;
|
||
};
|
||
|
||
export const AgentsTable = ({ cursor }) => (
|
||
<>
|
||
<O c="o-head">{"PANE AGENT STATUS MESSAGE"}</O>
|
||
{AGENT_ROWS.map(([p, a, s, m]) => {
|
||
const st = a === "cursor" ? cursor : s;
|
||
return <O key={p}>{p.padEnd(6)}{a.padEnd(9)}<span className={"o-" + st}>{st.padEnd(9)}</span>{m}</O>;
|
||
})}
|
||
</>
|
||
);
|
||
|
||
export const AGENT_ROWS = [
|
||
["%12", "claude", "working", "Spin up a staging instance"],
|
||
["%14", "codex", "working", "Inspect CLI error logs"],
|
||
["%21", "cursor", "working", "Refactor session auth"],
|
||
["%22", "gemini", "working", "Fix pricing fetch timeout"],
|
||
["%31", "claude", "done", "Optimize route planner"],
|
||
];
|
||
|
||
export const TermAgents = ({ t }) => {
|
||
const c1 = "tty7 agents", c2 = "tty7 wait %21 --until waiting";
|
||
return (
|
||
<div className="t7w-term t7w-mono">
|
||
<P caret={t < 1.3}>{typed(c1, t, 0.4, 18)}</P>
|
||
{t >= 1.3 ? <AgentsTable cursor="working" /> : null}
|
||
{t >= 1.5 ? <P caret={!typingDone(c2, t, 1.7, 26)}>{typed(c2, t, 1.7, 26)}</P> : null}
|
||
{t >= 3.1 ? <O>{"%21 "}<span className="o-waiting">waiting</span></O> : null}
|
||
{t >= 3.1 ? <P caret /> : null}
|
||
</div>
|
||
);
|
||
};
|
||
|
||
export const TermCli = ({ t }) => {
|
||
const a = "tty7 split --h", b = "tty7 send %12 'claude \"fix flaky test\"' --enter";
|
||
const c = "tty7 wait %12 --until done", d = "tty7 capture %12 --tail 1";
|
||
const split = t >= 1.3;
|
||
return (
|
||
<div className={"t7w-split" + (split ? " is-split" : "")}>
|
||
<div className="t7w-term t7w-mono">
|
||
<P caret={t < 1.3}>{typed(a, t, 0.4, 18)}</P>
|
||
{t >= 1.3 ? <O c="o-dim">%12</O> : null}
|
||
{t >= 1.5 ? <P caret={!typingDone(b, t, 1.6, 30)}>{typed(b, t, 1.6, 30)}</P> : null}
|
||
{t >= 3.3 ? <P caret={t >= 3.3 && t < 6.4 && typingDone(c, t, 3.5, 26)}>{typed(c, t, 3.5, 26)}</P> : null}
|
||
{t >= 6.5 ? <P caret={!typingDone(d, t, 6.6, 26)}>{typed(d, t, 6.6, 26)}</P> : null}
|
||
{t >= 7.7 ? <O c="o-done-ok">✓ Patched warmup() race · 214 passed</O> : null}
|
||
{t >= 7.7 ? <P caret /> : null}
|
||
</div>
|
||
{split ? (
|
||
<div className="t7w-term t7w-mono t7w-pane2">
|
||
<P caret={t < 3.2}>{t >= 3.2 ? "claude \"fix flaky test\"" : ""}</P>
|
||
{t >= 3.7 ? <O c="o-claude">✻ Reading cache_test.rs</O> : null}
|
||
{t >= 4.6 ? <O c="o-claude">✻ Running cargo test ×20</O> : null}
|
||
{t >= 5.6 ? <O c="o-done-ok">✓ Found race in warmup()</O> : null}
|
||
{t >= 6.3 ? <O c="o-done-ok">✓ Patched warmup() race · 214 passed</O> : null}
|
||
</div>
|
||
) : null}
|
||
</div>
|
||
);
|
||
};
|
||
|
||
export const COMPLETIONS = [
|
||
["checkout", "Switch branches or restore working tree files"],
|
||
["cherry-pick", "Apply the changes introduced by existing commits"],
|
||
["cherry", "Find commits yet to be applied upstream"],
|
||
];
|
||
|
||
export const TermPrompt = ({ t }) => {
|
||
const menu = t >= 1.8 && t < 3.6;
|
||
const sel = t < 2.4 ? 0 : t < 3.0 ? 1 : 0;
|
||
const accepted = t >= 4.5, ran = t >= 5.3;
|
||
let line;
|
||
if (t < 3.6) line = <>{typed("git ch", t, 0.4, 12)}{!menu && t >= 1.0 ? <span className="o-ghost">eckout -b fix/pricing</span> : null}</>;
|
||
else if (!accepted) line = <>{"git checkout "}{t >= 3.8 ? <span className="o-ghost">-b fix/pricing</span> : null}</>;
|
||
else line = <><b className="o-cmd">git</b> <span>checkout</span> <span className="o-flag">-b</span> <span className="o-arg">fix/pricing</span></>;
|
||
return (
|
||
<div className="t7w-term t7w-mono">
|
||
<P>cargo test --quiet</P>
|
||
<O c="o-dim">test result: ok. 214 passed; 0 failed</O>
|
||
<div className="t7w-anchor">
|
||
<P caret={!ran}>{line}</P>
|
||
{menu ? (
|
||
<div className="t7w-pop t7w-complete">
|
||
{COMPLETIONS.map(([n, d], i) => (
|
||
<div className={"t7w-crow" + (i === sel ? " is-sel" : "")} key={n}><span className="n">{n}</span><span className="d">{d}</span></div>
|
||
))}
|
||
</div>
|
||
) : null}
|
||
</div>
|
||
{ran ? <O>{"Switched to a new branch 'fix/pricing'"}</O> : null}
|
||
{ran ? <P br="fix/pricing" caret /> : null}
|
||
</div>
|
||
);
|
||
};
|
||
|
||
export const PAL = {
|
||
Terminals: [["claude", "Spin up a staging instance", "atlas-api"], ["codex", "Inspect CLI error logs", "atlas-api"], ["cursor", "Refactor session auth", "northwind · feat/session-auth"]],
|
||
Actions: [["Split Right", "⌘D"], ["Zoom Pane", "⇧⌘↩"], ["New Worktree Tab…", ""]],
|
||
Sessions: [["claude", "Handle empty daily summaries", "atlas-api · 12m ago"], ["codex", "Add export endpoints", "atlas-api · Yesterday"], ["claude", "Summarize benchmark results", "research · Last week"]],
|
||
};
|
||
|
||
export const Palette = ({ t }) => {
|
||
const q = typed("summ", t, 2.2, 7).toLowerCase();
|
||
const m = (s) => !q || s.toLowerCase().includes(q);
|
||
const groups = [
|
||
["Terminals", PAL.Terminals.filter((x) => m(x[1]))],
|
||
["Actions", PAL.Actions.filter((x) => m(x[0]))],
|
||
["Sessions", PAL.Sessions.filter((x) => m(x[1]))],
|
||
].filter(([, xs]) => xs.length);
|
||
if (q) groups.sort((x, y) => (x[0] === "Sessions" ? -1 : y[0] === "Sessions" ? 1 : 0));
|
||
let i = 0;
|
||
return (
|
||
<div className="t7w-pop t7w-palette">
|
||
<div className="t7w-pinput">
|
||
<Search />
|
||
{q ? <span>{q}<span className="t7w-caret" /></span> : <span className="ph"><span className="t7w-caret" />Search tabs, sessions, hosts and actions</span>}
|
||
</div>
|
||
<div className="t7w-scopes">{["All", "Actions", "Terminals", "Sessions", "Hosts"].map((s) => <span key={s} className={s === "All" ? "is-on" : ""}>{s}</span>)}</div>
|
||
<div className="t7w-presults">
|
||
{groups.map(([name, xs]) => (
|
||
<div key={name}>
|
||
<div className="t7w-pgroup">{name}</div>
|
||
{xs.map((x) => {
|
||
const sel = i++ === 0;
|
||
return name === "Actions" ? (
|
||
<div className={"t7w-prow" + (sel ? " is-sel" : "")} key={x[0]}><span className="t7w-glyph is-shell">{"›"}</span><span className="grow">{hl(x[0], q)}</span><span className="t7w-kbd-plain">{x[1]}</span></div>
|
||
) : (
|
||
<div className={"t7w-prow" + (sel ? " is-sel" : "")} key={x[1]}><Glyph a={x[0]} /><span className="grow">{hl(x[1], q)}</span><span className="meta">{x[2]}</span></div>
|
||
);
|
||
})}
|
||
</div>
|
||
))}
|
||
</div>
|
||
</div>
|
||
);
|
||
};
|
||
|
||
export const TermResumed = ({ t, t0, extra }) => (
|
||
<div className="t7w-term t7w-mono">
|
||
<P>{t >= t0 ? "claude --resume 3f9c2a" : ""}</P>
|
||
{t >= t0 + 0.5 ? <O c="o-claude">✻ Resuming “Handle empty daily summaries”</O> : null}
|
||
{t >= t0 + 1.2 ? <O c="o-dim"> Read report_service.py · 3 edits pending</O> : null}
|
||
{t >= t0 + 1.9 ? <O c="o-claude">✻ Running pytest tests/test_report_service.py</O> : null}
|
||
{extra}
|
||
</div>
|
||
);
|
||
|
||
export const TermSearch = ({ t }) => (t < 4.1
|
||
? (
|
||
<div className="t7w-term t7w-mono">
|
||
<P>tty7 agents</P>
|
||
<AgentsTable cursor="waiting" />
|
||
<P caret />
|
||
</div>
|
||
)
|
||
: <TermResumed t={t} t0={4.1} />);
|
||
|
||
export const TermPersist = ({ t }) => (
|
||
<TermResumed t={9} t0={0} extra={<>
|
||
{t >= 4.6 ? <O c="o-done-ok">✓ 18 passed · empty summaries return EMPTY_SUMMARY</O> : null}
|
||
{t >= 5.2 ? <O c="o-claude">⏺ Done. 3 files changed, +41 −6</O> : null}
|
||
</>} />
|
||
);
|
||
|
||
export const PERSIST_PANES = [["claude", "Handle empty daily summaries"], ["claude", "Spin up a staging instance"], ["codex", "Inspect CLI error logs"], ["gemini", "Fix pricing fetch timeout"]];
|
||
|
||
export const Frame = ({ ch, t }) => {
|
||
const k = CHAPTERS[ch].key;
|
||
const groups = sidebar(ch, t);
|
||
const title = (k === "search" && t >= 4.1) || k === "persist" ? "Handle empty daily summaries" : "~/code/orbit";
|
||
const gone = k === "persist" && t >= 0.9 && t < 3.7;
|
||
const palOpen = k === "search" && t >= 0.7 && t < 4.0;
|
||
const toast = k === "agents" && t >= 3.2 && t < 6.9;
|
||
return (
|
||
<div className="t7w-stagebox">
|
||
<div className={"t7w" + (gone ? " is-gone" : "")} aria-hidden="true">
|
||
<div className="t7w-lights"><span /><span /><span /></div>
|
||
<aside className="t7w-sb">
|
||
<div className="t7w-bar">
|
||
<span className="t7w-iconbtn"><svg width="11" height="11" viewBox="0 0 11 11"><path d="M5.5 1v9M1 5.5h9" stroke="var(--w-a45)" strokeWidth="1.2" strokeLinecap="round" /></svg></span>
|
||
</div>
|
||
<div className="t7w-sbtop">
|
||
<div className="t7w-ws"><span className="t7w-ws-init">W</span><span>work</span></div>
|
||
<div className="t7w-search"><Search /><span>Search</span></div>
|
||
</div>
|
||
<div className="t7w-groups">{groups.map((g) => <Group key={g.name} g={g} />)}</div>
|
||
</aside>
|
||
<main className="t7w-main">
|
||
<div className="t7w-bar">
|
||
<div className={"t7w-pal" + (k === "search" && t >= 0.35 && t < 0.7 ? " is-press" : "")}><Search /><span>{title}</span><span className="t7w-kbd">⌘P</span></div>
|
||
</div>
|
||
{k === "agents" ? <TermAgents t={t} /> : k === "cli" ? <TermCli t={t} /> : k === "prompt" ? <TermPrompt t={t} /> : k === "search" ? <TermSearch t={t} /> : <TermPersist t={t} />}
|
||
</main>
|
||
<Inspector />
|
||
{palOpen ? <div className="t7w-scrim" /> : null}
|
||
{palOpen ? <Palette t={t} /> : null}
|
||
{toast ? (
|
||
<div className="t7w-toast">
|
||
<img src="/logo/logo.svg" alt="" />
|
||
<div><b>Cursor</b><span>needs your permission…</span><span className="sub">northwind · Refactor session auth</span></div>
|
||
</div>
|
||
) : null}
|
||
</div>
|
||
{k === "persist" && t >= 1.1 && t < 3.8 ? (
|
||
<div className="t7w-afterlife">
|
||
<div className="t7w-al-title">Window closed. The server kept going.</div>
|
||
{PERSIST_PANES.map(([a, s]) => (
|
||
<div className="t7w-al-row" key={s}><Glyph a={a} /><span>{s}</span><span className="t7w-dot is-working" /></div>
|
||
))}
|
||
</div>
|
||
) : null}
|
||
</div>
|
||
);
|
||
};
|
||
|
||
{/* Mintlify documents useState and useEffect in MDX, so the mutable clock and the
|
||
element handle live in state rather than refs.
|
||
|
||
Keep JS comments out of every export body in this file, line and block comments alike: the
|
||
hosted Mintlify build fails to parse a page that has one, and shows only a
|
||
"parsing error" placeholder. mint dev and mint validate accept it, so the only
|
||
place it shows up is the live site. Comment at the top level, like this. */}
|
||
export const Demo = () => {
|
||
const [st, setSt] = useState({ ch: 0, t: 0 });
|
||
const [c0] = useState(() => ({ ch: 0, start: 0, visible: true, last: 0, paused: 0 }));
|
||
const [el, setEl] = useState(null);
|
||
useEffect(() => {
|
||
const c = c0;
|
||
c.start = performance.now();
|
||
let raf = 0;
|
||
const tick = (now) => {
|
||
raf = requestAnimationFrame(tick);
|
||
if (!c.visible || document.hidden) { c.start = now - c.paused; return; }
|
||
let t = (now - c.start) / 1000;
|
||
if (t >= CHAPTERS[c.ch].d) { c.ch = (c.ch + 1) % CHAPTERS.length; c.start = now; t = 0; }
|
||
c.paused = now - c.start;
|
||
if (now - c.last < 33) return;
|
||
c.last = now;
|
||
setSt({ ch: c.ch, t });
|
||
};
|
||
c.paused = 0;
|
||
raf = requestAnimationFrame(tick);
|
||
const io = new IntersectionObserver(([e]) => { c.visible = e.isIntersecting; }, { threshold: 0.05 });
|
||
if (el) io.observe(el);
|
||
return () => { cancelAnimationFrame(raf); io.disconnect(); };
|
||
}, [el]);
|
||
const go = (i) => {
|
||
const c = c0;
|
||
c.ch = i; c.start = performance.now(); c.paused = 0;
|
||
setSt({ ch: i, t: 0 });
|
||
};
|
||
const cur = CHAPTERS[st.ch];
|
||
return (
|
||
<div className="t7h-demo" ref={setEl}>
|
||
<Frame ch={st.ch} t={st.t} />
|
||
<div className="t7h-chapters" role="tablist" aria-label="tty7 demo">
|
||
{CHAPTERS.map((c, i) => (
|
||
<button key={c.key} role="tab" aria-selected={i === st.ch} className={"t7h-chapter" + (i === st.ch ? " is-on" : "")} onClick={() => go(i)}>
|
||
<span className="t7h-chapter-bar"><span style={{ width: (i === st.ch ? Math.min(1, st.t / cur.d) * 100 : i < st.ch ? 100 : 0) + "%" }} /></span>
|
||
<span className="t7h-chapter-label">{c.label}</span>
|
||
</button>
|
||
))}
|
||
</div>
|
||
<div className="t7h-chapter-now">{cur.label}</div>
|
||
</div>
|
||
);
|
||
};
|
||
|
||
export const Ico = ({ d }) => (
|
||
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="var(--primary-text)" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round"><path d={d} /></svg>
|
||
);
|
||
|
||
export const FEATURES = [
|
||
{ t: "Sessions that outlive the app", href: "/getting-started/concepts",
|
||
d: "M12 3v8M6.3 6.3a8 8 0 1 0 11.4 0",
|
||
p: "A background server owns your shells, not the window. Quit tty7 and your builds keep building. No tmux to learn or configure." },
|
||
{ t: "Editor-grade input", href: "/terminal/prompt",
|
||
d: "M5 6h14a2 2 0 0 1 2 2v8a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2zM7 10h.01M11 10h.01M15 10h.01M8 14h8",
|
||
p: "Ghost suggestions from your history, tab completion that explains each flag, syntax highlighting, click-to-place caret, real multi-line editing." },
|
||
{ t: "Agent-aware", href: "/agents/overview",
|
||
d: "M8 8h8a3 3 0 0 1 3 3v5a3 3 0 0 1-3 3H8a3 3 0 0 1-3-3v-5a3 3 0 0 1 3-3zM12 4v4M9.5 13h.01M14.5 13h.01",
|
||
p: "26 coding CLIs recognised on sight. Per-pane status dots, a notification when one needs you, and session resume after a reboot." },
|
||
{ t: "Remote work that feels local", href: "/remote/workspaces",
|
||
d: "M5 4h14a2 2 0 0 1 2 2v3a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2zM5 13h14a2 2 0 0 1 2 2v3a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-3a2 2 0 0 1 2-2zM7 7.5h.01M7 16.5h.01",
|
||
p: "A native Rust SSH stack with profiles, SFTP, and port forwarding — plus remote workspaces where files, repos, and panes stay on the far machine." },
|
||
{ t: "Git where you are looking", href: "/git/source-control",
|
||
d: "M6 3a2 2 0 1 0 0 4 2 2 0 0 0 0-4zM6 17a2 2 0 1 0 0 4 2 2 0 0 0 0-4zM18 5a2 2 0 1 0 0 4 2 2 0 0 0 0-4zM6 7v10M18 9v1a4 4 0 0 1-4 4H6",
|
||
p: "Branch and diff counts on every sidebar row, a source control panel, a diff overlay, GitHub issues and pull requests, and worktrees in one dialog." },
|
||
{ t: "Scriptable", href: "/cli/overview",
|
||
d: "M5 4h14a2 2 0 0 1 2 2v12a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2zM7 9l3 3-3 3M13 15h4",
|
||
p: "A bundled tty7 CLI that opens panes, sends keys, reads screens, and blocks until an agent needs you — so scripts and agents can drive the workbench." },
|
||
];
|
||
|
||
export const Features = () => (
|
||
<div className="t7h-grid">
|
||
{FEATURES.map((f) => (
|
||
<a className="t7h-card" href={f.href} key={f.t}>
|
||
<span className="t7h-card-icon"><Ico d={f.d} /></span>
|
||
<h3>{f.t}</h3>
|
||
<p>{f.p}</p>
|
||
</a>
|
||
))}
|
||
</div>
|
||
);
|
||
|
||
export const Tick = () => (
|
||
<span className="t7h-tick"><svg width="10" height="10" viewBox="0 0 12 12"><path d="M2.5 6.2l2.3 2.3 4.7-5" fill="none" stroke="var(--primary-text)" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round" /></svg></span>
|
||
);
|
||
|
||
export const AgentPanel = () => (
|
||
<div className="t7h-panel" role="img" aria-label="Agent tabs in the tty7 sidebar, each with a live status dot, and a notification that Cursor is waiting for input">
|
||
{GROUPS.slice(0, 2).map((g) => <Group key={g.name} g={g} />)}
|
||
<div className="t7h-toast">
|
||
<Glyph a="cursor" />
|
||
<div><b>Cursor needs your permission…</b><span>northwind · Refactor session auth</span></div>
|
||
<span className="t7w-kbd">⌘1</span>
|
||
</div>
|
||
</div>
|
||
);
|
||
|
||
export const Bench = ({ title, hint, big, unit, rows, max }) => (
|
||
<div className="t7h-bench">
|
||
<div className="t7h-bench-top"><h3>{title}</h3><span>{hint}</span></div>
|
||
<div className="t7h-big">{big}<small>{unit}</small></div>
|
||
<div className="t7h-bars">
|
||
{rows.map(([n, v]) => (
|
||
<div className={"t7h-barrow" + (n === "tty7" ? " is-us" : "")} key={n}>
|
||
<span>{n}</span>
|
||
<span className="t7h-track"><span className="t7h-fill" style={{ display: "block", width: (v / max) * 100 + "%" }} /></span>
|
||
<span className="v">{v} {unit}</span>
|
||
</div>
|
||
))}
|
||
</div>
|
||
</div>
|
||
);
|
||
|
||
export const STEPS = [
|
||
{ t: "Install it", p: "Native builds for macOS, Windows, and Linux. No runtime to install first.", href: "/getting-started/installation", l: "Installation" },
|
||
{ t: "Set it up", p: "Five minutes of settings that pay for themselves: shell, font, theme, keys.", href: "/getting-started/first-launch", l: "First launch" },
|
||
{ t: "Learn the three words", p: "Workspace, tab, pane — and the server underneath them that keeps it all alive.", href: "/getting-started/concepts", l: "Core concepts" },
|
||
];
|
||
|
||
export const Home = () => (
|
||
<div className="t7h">
|
||
<section className="t7h-hero">
|
||
<div className="t7h-wrap">
|
||
<span className="t7h-eyebrow"><i />Open source · written in Rust</span>
|
||
<h1>A terminal you can leave running.</h1>
|
||
<p className="t7h-lede">Close the window, reboot, walk to a different laptop — the shells you started are still there, and so are the coding agents you left working in them.</p>
|
||
<div className="t7h-ctas">
|
||
<a className="t7h-btn t7h-btn-primary" href="https://github.com/l0ng-ai/tty7/releases">Download tty7</a>
|
||
<a className="t7h-btn t7h-btn-ghost" href="/introduction">Read the docs →</a>
|
||
</div>
|
||
<div className="t7h-platforms">macOS · Windows · Linux</div>
|
||
</div>
|
||
<div className="t7h-stage"><Demo /></div>
|
||
</section>
|
||
|
||
<section className="t7h-section">
|
||
<div className="t7h-wrap">
|
||
<div className="t7h-kicker">What makes it different</div>
|
||
<h2 className="t7h-h2">A workbench, not just a window.</h2>
|
||
<p className="t7h-sub">Everything you reach for between commands — sessions, hosts, branches, agents — sits next to the terminal instead of in another app.</p>
|
||
<Features />
|
||
</div>
|
||
</section>
|
||
|
||
<section className="t7h-section">
|
||
<div className="t7h-wrap t7h-split">
|
||
<div>
|
||
<div className="t7h-kicker">Coding agents</div>
|
||
<h2 className="t7h-h2">Run five agents. Watch none of them.</h2>
|
||
<p className="t7h-sub">tty7 never wraps your agents — it recognises them by the command in the pane and keeps track from the outside.</p>
|
||
<ul className="t7h-list">
|
||
<li><Tick /><span><b>Status on every tab.</b> Working, waiting, done — a dot per pane, grouped by repository.</span></li>
|
||
<li><Tick /><span><b>Told when you're needed.</b> A notification the moment an agent blocks on you, and another when a turn finishes.</span></li>
|
||
<li><Tick /><span><b>Back after a reboot.</b> Agent sessions resume where they were, in the pane they were in.</span></li>
|
||
</ul>
|
||
<div className="t7h-chips">
|
||
{["claude", "codex", "gemini", "cursor", "opencode", "copilot"].map((a) => (
|
||
<span className="t7h-chip" key={a}><Glyph a={a} />{AGENTS[a].name}</span>
|
||
))}
|
||
<a className="t7h-chip is-more" href="/agents/overview">+20 more</a>
|
||
</div>
|
||
</div>
|
||
<AgentPanel />
|
||
</div>
|
||
</section>
|
||
|
||
<section className="t7h-section">
|
||
<div className="t7h-wrap">
|
||
<div className="t7h-kicker">Performance</div>
|
||
<h2 className="t7h-h2">Roughly twice as fast where it counts.</h2>
|
||
<p className="t7h-sub">The PTY is read at device speed and parsed off the render path, so drawing never throttles reading — and a flood never drops the frame rate.</p>
|
||
<div className="t7h-stats">
|
||
<Bench title="Plaintext I/O, 11 MB cat" hint="lower is better" big="95" unit="ms" max={239}
|
||
rows={[["tty7", 95], ["Ghostty", 179], ["Kitty", 185], ["Alacritty", 239]]} />
|
||
<Bench title="DOOM-fire frame rate" hint="higher is better" big="888" unit="fps" max={888}
|
||
rows={[["tty7", 888], ["Kitty", 617], ["Ghostty", 552], ["Alacritty", 485]]} />
|
||
</div>
|
||
<p className="t7h-footnote">Apple M1 Pro, macOS 26.3.1, 155×40 grid, five-run averages. <a href="https://github.com/l0ng-ai/tty7/tree/main/scripts/bench">Reproduce it with one command →</a></p>
|
||
</div>
|
||
</section>
|
||
|
||
<section className="t7h-section">
|
||
<div className="t7h-wrap">
|
||
<div className="t7h-kicker">Start here</div>
|
||
<h2 className="t7h-h2">Up and running in three steps.</h2>
|
||
<div className="t7h-steps">
|
||
{STEPS.map((s, i) => (
|
||
<a className="t7h-card" href={s.href} key={s.t}>
|
||
<span className="t7h-step-n">{i + 1}</span>
|
||
<h3>{s.t}</h3>
|
||
<p>{s.p}</p>
|
||
<span className="t7h-more">{s.l} →</span>
|
||
</a>
|
||
))}
|
||
</div>
|
||
</div>
|
||
</section>
|
||
|
||
<div className="t7h-wrap">
|
||
<section className="t7h-end">
|
||
<h2 className="t7h-h2">Your shells will still be there.</h2>
|
||
<p className="t7h-sub">Free and open source. Native on macOS, Windows, and Linux.</p>
|
||
<div className="t7h-ctas">
|
||
<a className="t7h-btn t7h-btn-primary" href="https://github.com/l0ng-ai/tty7/releases">Download tty7</a>
|
||
<a className="t7h-btn t7h-btn-ghost" href="/introduction">Read the docs →</a>
|
||
</div>
|
||
<div className="t7h-end-links">
|
||
<a href="https://github.com/l0ng-ai/tty7">GitHub</a>
|
||
<a href="https://discord.gg/s3dethqz2V">Discord</a>
|
||
<a href="https://github.com/l0ng-ai/tty7/blob/main/CHANGELOG.md">Changelog</a>
|
||
</div>
|
||
</section>
|
||
</div>
|
||
</div>
|
||
);
|
||
|
||
<Home />
|