Skip to content

Where people and agents ship software together. The open-source git platform for the whole job: issues, agents, checks and deploys to the edge.

Commit

The landing page starts with Home, where the day starts: the hero tour opens on Priya's Home (notifications that settled overnight leave Where you're needed, Start here picks her pull request in review, What changed says what landed and is live), then she opens #431 from the summary and watches it pass its checks, get approved and merge, then its session, then #web hearing it shipped. Home's two pillars (Where you're needed, What changed) move out of Agents into a Home section right after the facts, and One workspace names Home first. The tour no longer stamps "Works today" under each step or says "Everything here works today"; the product is live. Checked: node --test app/lib/tour.test.ts (10); apps/web tsc -b; the landing page rendered in the dev server, tour stepped through Home, #431, its session and #web.

G1t-Session:
ses_01m4z8d9age2yr4rdbcd4d8pwq
syntaqxcommitted Parent132e163Browse files
4 files+165−2060/4 viewed
+64−48
201201 );
202202 }
203203
204−/** Facts under the opening, each one true today. */
204+/** Facts under the opening. */
205205 const FACTS: [string, string][] = [
206206 [
207207 "$0",
454454 />
455455 </section>
456456
457− {/* Facts, each one true today. */}
457+ {/* Facts. */}
458458 <section className="relative z-10 mx-auto max-w-6xl px-4">
459459 <dl className="grid gap-px overflow-hidden rounded-2xl border border-line bg-line sm:grid-cols-2 lg:grid-cols-4">
460460 {FACTS.map(([figure, about]) => (
468468 </dl>
469469 </section>
470470
471+ {/* Home: where the day starts. */}
472+ <section id="home" className="mx-auto max-w-6xl scroll-mt-20 px-4 pt-24">
473+ <div className="max-w-3xl">
474+ <Eyebrow>Home</Eyebrow>
475+ <h2 className="mt-3 text-3xl font-semibold tracking-tight text-balance sm:text-5xl">
476+ Start every day knowing where things stand.
477+ </h2>
478+ <p className="mt-5 max-w-2xl text-lg leading-8 text-muted">
479+ Home is your daily update. g1t keeps track of everything your team
480+ and its coding agents did, clears what resolved itself, tells you
481+ in plain English what changed and what is live, and asks you only
482+ for what is yours to decide: a merge, a review, a fix.
483+ </p>
484+ </div>
485+
486+ <div className="mt-6 divide-y divide-line">
487+ <Pillar
488+ eyebrow="Where you're needed"
489+ title="Only what still needs you"
490+ art={<NeedsYouArt />}
491+ points={[
492+ "Notifications that settle themselves",
493+ "Why each one settled, and who did it",
494+ "Pull requests ready for you to merge",
495+ "Reviews asked of you, by name",
496+ "Start here: the one to do first, and why",
497+ ]}
498+ more={["Home", `${DOCS}/guides/home/`]}
499+ >
500+ A notification is done once a later event resolves it: the next run
501+ passes, the pull request merges, the fix deploys. g1t settles it
502+ for you, and Home shows only what still waits on you. Start here
503+ picks the one to do first and opens it.
504+ </Pillar>
505+
506+ <Pillar
507+ flip
508+ eyebrow="What changed"
509+ title="Come back to what changed, in plain English"
510+ art={<WhatChangedArt />}
511+ points={[
512+ "A summary of what landed since you were last here",
513+ "Whether each change is live, deploying or failed",
514+ "Explain on any change that landed",
515+ "Ask a follow-up, in your direct message with g1t",
516+ ]}
517+ more={["Ask g1t", `${DOCS}/guides/ask/`]}
518+ >
519+ Home asks &ldquo;What are we building today?&rdquo; Press
520+ Summarize and What changed reads the commits, pull requests, deploys
521+ and conversations since you were last here, then tells you what
522+ changed, why, and what is live. Explain any one change, or ask a
523+ follow-up. It runs on your workspace&apos;s own AI provider.
524+ </Pillar>
525+ </div>
526+ </section>
527+
471528 {/* What a workspace holds. */}
472529 <section className="mx-auto max-w-6xl px-4 pt-24">
473530 <div className="max-w-3xl">
476533 Talk, write it down, ship.
477534 </h2>
478535 <p className="mt-5 max-w-2xl text-lg leading-8 text-muted">
479− Code, Chat and Artifacts share one set of members, one sign-in and
480− one bill. The coding agents you connect work as you, through MCP,
536+ Home, Code, Chat and Artifacts share one set of members, one
537+ sign-in and one bill. The coding agents you connect work as you, through MCP,
481538 the g1t CLI and the API.
482539 </p>
483540 </div>
649706 Agents do the work. You make the calls.
650707 </h2>
651708 <p className="mt-5 max-w-2xl text-lg leading-8 text-muted">
652− Your coding agents push the branches and open the pull requests.
653− g1t keeps track of all of it, clears what resolved itself, and asks
654− you only for what is yours to decide: a merge, a review, a fix.
709+ Your coding agents push the branches and open the pull requests,
710+ and their work lands through the same checks as everyone&apos;s.
711+ You decide what they take on and what ships.
655712 </p>
656− </div>
657−
658− <div className="mt-6 divide-y divide-line">
659− <Pillar
660− eyebrow="Where you're needed"
661− title="Only what still needs you"
662− art={<NeedsYouArt />}
663− points={[
664− "Notifications that settle themselves",
665− "Why each one settled, and who did it",
666− "Pull requests ready for you to merge",
667− "Reviews asked of you, by name",
668− "Start here: the one to do first, and why",
669− ]}
670− more={["Home", `${DOCS}/guides/home/`]}
671− >
672− A notification is done once a later event resolves it: the next run
673− passes, the pull request merges, the fix deploys. g1t settles it
674− for you, and Home shows only what still waits on you. Start here
675− picks the one to do first and opens it.
676− </Pillar>
677−
678− <Pillar
679− flip
680− eyebrow="What changed"
681− title="Come back to what changed, in plain English"
682− art={<WhatChangedArt />}
683− points={[
684− "A summary of what landed since you were last here",
685− "Whether each change is live, deploying or failed",
686− "Explain on any change that landed",
687− "Ask a follow-up, in your direct message with g1t",
688− ]}
689− more={["Ask g1t", `${DOCS}/guides/ask/`]}
690− >
691− Home asks &ldquo;What are we building today?&rdquo; Press
692− Summarize and What changed reads the commits, pull requests, deploys
693− and conversations since you were last here, then tells you what
694− changed, why, and what is live. Explain any one change, or ask a
695− follow-up. It runs on your workspace&apos;s own AI provider.
696− </Pillar>
697713 </div>
698714
699715 <div className="mt-10 grid gap-4 lg:grid-cols-2">
+34−90
11 /**
22 * The landing page's product tour: g1t browsing itself. A faithful app
33 * frame (rail, sidebars, the real colours) plays one story on a loop, with
4− * a soft cursor that moves and clicks: the pull request going green and
5− * merging, the session its author's coding agent recorded onto it, Home
6− * when its author comes back (what settled by itself, what still needs her,
7− * and What changed in plain English), and the people in #web hearing it
8− * shipped.
4+ * a soft cursor that moves and clicks: Home as its author starts her day
5+ * (what settled by itself, what still needs her, and What changed in plain
6+ * English), her pull request going green and merging, the session her
7+ * coding agent recorded onto it, and the people in #web hearing it shipped.
98 *
109 * The frame is a pure function of time (lib/tour.ts). One rAF loop drives
1110 * the clock and re-renders only when the frame changes; the step pills'
2120 * the conversation's own header as the app does, and the app's own cards.
2221 * The coding agent is one people bring: its commits carry its mark, as
2322 * components/coding-agent.tsx draws it, and its session is the pull
24− * request's Session tab. Everything the tour shows works today.
23+ * request's Session tab.
2524 */
2625 import {
2726 Activity,
131130 const motion = window.matchMedia("(prefers-reduced-motion: reduce)");
132131 if (motion.matches) {
133132 setReduced(true);
134− const next = frameAt(STILLS.code);
133+ const next = frameAt(STILLS.home);
135134 shown.current = next;
136135 setFrame(next);
137136 return;
195194 /** The pill a frame belongs to, from what it shows (the clock's time is not React state). */
196195 function pillOf(frame: Frame): number {
197196 return Math.max(0, PILLS.findIndex((pill) => pill.scene === frame.scene));
198−}
199−
200−/** What the line under the frame says about the moment. Everything in the tour works today. */
201−const HONESTY: Record<Scene, string> = {
202− code: "Works today",
203− session: "Works today: recorded by your coding agent through MCP",
204− home: "Works today: summaries run on your workspace's own AI provider",
205− chat: "Works today",
206−};
207−
208−/** The line under the frame. Not part of the app, so it sits outside it. */
209−function HonestyNote({ frame, className }: { frame: Frame; className?: string }) {
210− const label = HONESTY[frame.scene];
211− return (
212− <span
213− key={label}
214− className={cn(
215− "tour-fade inline-flex items-center gap-1.5 rounded-full bg-success/10 px-2.5 py-1 text-[11px] font-medium text-success ring-1 ring-success/25",
216− className,
217− )}
218− >
219− <span className="size-1.5 rounded-full bg-success" />
220− {label}
221− </span>
222− );
223197 }
224198
225199 /* ------------------------------------------------------------------ */
258232 );
259233 }
260234
261−/** Text with @mentions, #channels and #numbers picked out, as chat shows them. */
262−function Rich({ text }: { text: string }) {
235+/** Text with @mentions, #channels and #numbers picked out, as chat shows them; `targets` marks some for the cursor. */
236+function Rich({ text, targets }: { text: string; targets?: Record<string, CursorTarget> }) {
263237 const parts = text.split(/(@[a-z0-9-]+|#[a-z0-9]+)/gi);
264238 return (
265239 <>
266240 {parts.map((part, index) =>
267241 /^[@#]/.test(part) ? (
268− <span key={index} className={part.startsWith("@") ? "rounded bg-accent/15 px-0.5 text-accent" : "text-accent"}>
242+ <span key={index} data-tour={targets?.[part]} className={part.startsWith("@") ? "rounded bg-accent/15 px-0.5 text-accent" : "text-accent"}>
269243 {part}
270244 </span>
271245 ) : (
297271
298272 /** The rail's modes, in the app's order (components/rail.tsx), with its icons. */
299273 const RAIL: { key: string; icon: ReactNode; label: string; target?: CursorTarget; scenes?: Scene[]; badge?: number }[] = [
300− { key: "home", icon: <House size={15} />, label: "Home", target: "rail-home", scenes: ["home"] },
274+ { key: "home", icon: <House size={15} />, label: "Home", scenes: ["home"] },
301275 { key: "chat", icon: <MessagesSquare size={15} />, label: "Chat", target: "rail-chat", scenes: ["chat"], badge: 3 },
302276 { key: "code", icon: <Code2 size={15} />, label: "Code", target: "rail-code", scenes: ["code", "session"] },
303277 { key: "artifacts", icon: <Shapes size={15} />, label: "Artifacts" },
842816
843817 const HOME_ROW = "flex items-center gap-2.5 border-t border-line px-4 py-2";
844818
845−/** What still waits on Priya once the notifications above settle: her pull request ready to merge, and a review asked of her. */
819+/** What still waits on Priya once the notifications above settle: her pull request in review, and a review asked of her. */
846820 const STAYING = [
847− { icon: <GitMerge size={14} className="text-success" />, title: "Retry webhook deliveries with backoff", detail: "acme/web #433 · Ready to land when you merge it.", stake: "Ready to merge", action: "Open" },
821+ { icon: <GitPullRequest size={14} className="text-success" />, title: "Stream CSV exports in batches", detail: "acme/web #431 · Checks running · Dana is reviewing", stake: "Yours", action: "Open" },
848822 { icon: <GitPullRequest size={14} className="text-info" />, title: "Add audit log export", detail: "acme/web #435 · Your review", stake: "+212 −40", action: "Review" },
849823 ];
850824
899873 );
900874 }
901875
902−/** Start here: the deploy failure while it is open (it comes before anything else), then the pull request ready to merge. */
876+/** Start here: the deploy failure while it is open (it comes before anything else), then Priya's pull request. */
903877 function StartCard({ frame }: { frame: Frame }) {
904878 const deploy = frame.settled === 0;
905879 const pick = deploy
906880 ? { icon: <Rocket size={14} className="text-danger" />, title: SETTLED[0].title, next: "See the build", why: "Production is serving an older build until this is fixed, so it comes before anything else.", button: "See the build" }
907− : { icon: <GitMerge size={14} className="text-success" />, title: STAYING[0].title, next: "Merge", why: "Everything it needs is met; it lands when you merge it.", button: "Open pull request" };
881+ : { icon: <GitPullRequest size={14} className="text-success" />, title: STAYING[0].title, next: "Lands when Dana approves", why: "Dana's report in #web is waiting on it. Once its checks pass and she approves, the merge queue lands it.", button: "Open pull request" };
908882 return (
909883 <HomeCard
910884 title={
922896 <dt className="text-muted">Next</dt>
923897 <dd className="text-fg-soft">{pick.next}</dd>
924898 <dt className="text-muted">At stake</dt>
925− <dd className={deploy ? "text-danger" : "text-fg-soft"}>{deploy ? "Production on an older build" : "Ready to merge"}</dd>
899+ <dd className={deploy ? "text-danger" : "text-fg-soft"}>{deploy ? "Production on an older build" : "Big accounts' exports time out"}</dd>
926900 </dl>
927901 <p className="mt-2.5 rounded-lg bg-raised/60 px-2.5 py-2 text-[10.5px] leading-relaxed text-fg-soft">
928902 <span className="font-medium text-fg">Why this one: </span>
962936 ) : (
963937 <>
964938 <p className="text-[12.5px] leading-[1.6] text-fg-soft">
965− <Rich text={text} />
939+ <Rich text={text} targets={{ "#431": "home-pr" }} />
966940 {!frame.summarized && <span className="tour-caret ml-0.5 inline-block h-3.5 w-1.5 translate-y-0.5 rounded-[1px] bg-accent" />}
967941 </p>
968942 <div className={cn("mt-3 flex items-center gap-1.5 border-t border-line pt-2.5 transition-opacity duration-500", frame.summarized ? "opacity-100" : "opacity-0")}>
982956 );
983957 }
984958
985−/** Landed: each change with whether it is live, the agent's mark on the one it helped make, and Explain. */
986−function LandedCardMock({ frame }: { frame: Frame }) {
959+/** Landed: each change that came in overnight with whether it is live, the agent's mark on the one it helped make, and Explain. */
960+function LandedCardMock() {
987961 const rows = [
988− {
989− title: "Stream CSV exports in batches",
990− detail: "web#431 · by priya",
991− agent: true,
992− explain: "home-explain" as const,
993− badge: frame.live ? (
994− <Badge key="live" tone="success" className="tour-pop">
995− <Rocket size={9} />
996− Live 9 min after merge
997− </Badge>
998− ) : (
999− <Badge key="deploying" tone="info">
1000− <Loader size={9} className="tour-spin" />
1001− Deploying
1002− </Badge>
1003− ),
1004− at: "9:41 AM",
1005− },
1006− {
1007− title: "Pin the build image's cache key",
1008− detail: "web · 1 push by jordan",
1009− agent: false,
1010− explain: undefined,
1011− badge: (
1012− <Badge tone="success">
1013− <Rocket size={9} />
1014− Live 4 min after push
1015− </Badge>
1016− ),
1017− at: "8:15 AM",
1018− },
962+ { title: "Pin the build image's cache key", detail: "web · 1 push by jordan", agent: false, live: "Live 4 min after push", at: "8:15 AM" },
963+ { title: "Retry webhook deliveries with backoff", detail: "web#433 · by alex", agent: true, live: "Live 6 min after merge", at: "7:52 AM" },
1019964 ];
1020965 return (
1021966 <HomeCard title="Landed" hint="1 pull request merged · 1 push">
1029974 </p>
1030975 <div className="mt-0.5 flex min-w-0 flex-wrap items-center gap-x-2 gap-y-1">
1031976 <span className="truncate text-[10.5px] text-muted">{row.detail}</span>
1032− {row.badge}
977+ <Badge tone="success">
978+ <Rocket size={9} />
979+ {row.live}
980+ </Badge>
1033981 </div>
1034982 </div>
1035− <span data-tour={row.explain} className="flex size-6 shrink-0 items-center justify-center rounded-md text-muted">
983+ <span className="flex size-6 shrink-0 items-center justify-center rounded-md text-muted">
1036984 <Sparkles size={12} />
1037985 </span>
1038986 <span className="shrink-0 text-[10.5px] text-faint tabular-nums">{row.at}</span>
1043991 }
1044992
1045993 /**
1046− * Home, as Priya comes back to it (routes/workspace/home.tsx): "What are
994+ * Home, as Priya starts her day on it (routes/workspace/home.tsx): "What are
1047995 * we building today?" over the box that asks g1t, Where you're needed
1048996 * beside Start here, then Since you were last here with What changed and
1049997 * Landed. Pressing the first starter brings What changed into view, as the
11121060 <p className="truncate text-[11px] text-muted">Yesterday at 6:12 PM · 15 hours</p>
11131061 </div>
11141062 <WhatChangedCardMock frame={frame} />
1115− <LandedCardMock frame={frame} />
1063+ <LandedCardMock />
11161064 </div>
11171065 </div>
11181066 );
11931141 observer.observe(element);
11941142 return () => observer.disconnect();
11951143 }, []);
1196− const scenes: Scene[] = ["code", "session", "home", "chat"];
1144+ const scenes: Scene[] = ["home", "code", "session", "chat"];
11971145 const mains: Record<Scene, ReactNode> = {
11981146 chat: <ChatMain frame={frame} />,
11991147 session: <SessionMain frame={frame} />,
12591207 ];
12601208
12611209 function Phone({ frame }: { frame: Frame }) {
1262− const scenes: Scene[] = ["code", "session", "home", "chat"];
1210+ const scenes: Scene[] = ["home", "code", "session", "chat"];
12631211 return (
12641212 <div className="relative flex h-136 w-full flex-col overflow-hidden rounded-2xl bg-bg text-left text-fg shadow-2xl shadow-black/50 ring-1 ring-line scheme-dark">
12651213 <div className="flex h-12 shrink-0 items-center gap-2 border-b border-line px-3">
14311379
14321380 /**
14331381 * The hero's tour: the frame (a full app on wider screens, one pane on a
1434− * phone), the step pills under it, and a line saying what is a preview.
1382+ * phone), the step pills under it, and a line about the story.
14351383 */
14361384 export function ProductTour({ className }: { className?: string }) {
14371385 const clock = useTourClock();
14411389 <style dangerouslySetInnerHTML={{ __html: STYLES }} />
14421390 <div
14431391 role="img"
1444− aria-label="A tour of g1t: a pull request passes its checks, is approved and is merged by the merge queue, and its session from the coding agent that helped write it is replayed. Back on Home, notifications that settled themselves leave Where you're needed, Start here picks the pull request ready to merge, and What changed sums up in plain English what landed and that it is live. Then the team hears it shipped in a channel."
1392+ aria-label="A tour of g1t: on Home, notifications that settled themselves overnight leave Where you're needed, Start here picks the pull request in review, and What changed sums up in plain English what landed and that it is live. The pull request then passes its checks, is approved and is merged by the merge queue, and the session from the coding agent that helped write it is replayed. Then the team hears it shipped in a channel."
14451393 onMouseEnter={() => clock.hover(true)}
14461394 onMouseLeave={() => clock.hover(false)}
14471395 >
14511399 <div className="md:hidden">
14521400 <Phone frame={frame} />
14531401 </div>
1454− </div>
1455− <div className="mt-4 flex justify-center" aria-live="polite">
1456− <HonestyNote frame={frame} />
14571402 </div>
1458− <div className="mx-auto mt-3 grid max-w-xl grid-cols-4 gap-2" role="tablist" aria-label="Steps of the tour">
1403+ <div className="mx-auto mt-4 grid max-w-xl grid-cols-4 gap-2" role="tablist" aria-label="Steps of the tour">
14591404 {PILLS.map((pill, index) => {
14601405 const on = clock.pill === index;
14611406 return (
14861431 })}
14871432 </div>
14881433 <p className="mx-auto mt-3 max-w-xl text-center text-xs leading-5 text-faint text-balance">
1489− Everything here works today. Priya wrote the fix with Claude Code, connected to g1t through its MCP server: the
1490− commits carry its mark, and the session it recorded stays on the pull request. Home&apos;s summary runs on the
1491− workspace&apos;s own AI provider.
1434+ Priya&apos;s day starts on Home. She wrote the fix with Claude Code, connected to g1t through its MCP server: the
1435+ commits carry its mark, and the session it recorded stays on the pull request.
14921436 </p>
14931437 </div>
14941438 );
+20−16
33
44 import { BEAT, CHECKS, LOOP_MS, PILLS, SESSION, SETTLED, STILLS, SUMMARIZED, SUMMARY_WORDS, frameAt, pillAt, pillProgress, sameFrame } from "./tour.ts";
55
6−test("the first frame is the pull request before its checks, the same on the server and the client", () => {
6+test("the first frame is Home before anything settles, the same on the server and the client", () => {
77 const frame = frameAt(0);
8− assert.equal(frame.scene, "code");
8+ assert.equal(frame.scene, "home");
9+ assert.equal(frame.settled, 0);
10+ assert.equal(frame.summarizing, false);
911 assert.equal(frame.checks, 0);
10− assert.equal(frame.merged, false);
1112 assert.equal(frame.cursorShown, false);
1213 assert.ok(sameFrame(frame, frameAt(0)));
1314 });
1415
15−test("the scenes come in the story's order: the pull request first", () => {
16+test("the scenes come in the story's order: Home first", () => {
1617 const order: string[] = [];
1718 for (let t = 0; t < LOOP_MS; t += 100) {
1819 const scene = frameAt(t).scene;
1920 if (order[order.length - 1] !== scene) order.push(scene);
2021 }
21− assert.deepEqual(order, ["code", "session", "home", "chat"]);
22+ assert.deepEqual(order, ["home", "code", "session", "chat"]);
2223 });
2324
2425 test("checks pass before review, and the queue merges only after approval", () => {
2728 assert.equal(frameAt(BEAT.merged).approved, true);
2829 });
2930
30−test("the cursor leaves the code only once it has merged, for the Session tab", () => {
31+test("Home's #431 opens the pull request, and the cursor stays out of its way until it has merged", () => {
32+ assert.equal(frameAt(BEAT.prClick).cursor, "home-pr");
33+ assert.equal(frameAt(BEAT.prClick).click, true);
34+ assert.equal(frameAt(BEAT.prClick).scene, "home");
35+ assert.equal(frameAt(BEAT.code).scene, "code");
36+ assert.equal(frameAt(BEAT.code).checks, 0);
3137 assert.equal(frameAt(BEAT.merged - 1).cursorShown, false);
3238 assert.equal(frameAt(BEAT.sessionClick).cursor, "pr-session");
3339 assert.equal(frameAt(BEAT.sessionClick).click, true);
3541 assert.equal(frameAt(BEAT.railChatClick).click, true);
3642 });
3743
38−test("the session plays back in full before the tour moves on to Home", () => {
44+test("the session plays back in full before the tour moves on to Chat", () => {
3945 assert.equal(frameAt(BEAT.session - 1).entries, 0);
40− assert.equal(frameAt(BEAT.railHomeClick - 1).entries, SESSION.length);
46+ assert.equal(frameAt(BEAT.railChatClick - 1).entries, SESSION.length);
4147 assert.equal(SESSION[0].kind, "prompt");
4248 });
4349
4753 });
4854
4955 test("pills start where their scene does, and progress runs 0 to 1", () => {
50− assert.equal(PILLS[0].scene, "code");
56+ assert.equal(PILLS[0].scene, "home");
5157 PILLS.forEach((pill, index) => {
5258 assert.equal(frameAt(pill.start + 1).scene, pill.scene);
5359 assert.equal(pillAt(pill.start + 1), index);
6975 assert.equal(frameAt(BEAT.told).shipped, true);
7076 });
7177
72−test("on Home, notifications settle and leave before Summarize, and the summary finishes before chat", () => {
73− assert.equal(frameAt(BEAT.home - 1).settled, 0);
74− assert.equal(frameAt(BEAT.home + 1).settled, 0);
78+test("on Home, notifications settle and leave before Summarize, and the summary finishes before #431 is opened", () => {
79+ assert.equal(frameAt(BEAT.settle0 - 1).settled, 0);
7580 const before = frameAt(BEAT.summarizeClick);
7681 assert.equal(before.settled, SETTLED.length);
7782 assert.equal(before.cleared, SETTLED.length);
7883 assert.equal(before.cursor, "home-summarize");
7984 assert.equal(before.click, true);
8085 assert.equal(frameAt(BEAT.word0 - 1).words, 0);
81− assert.ok(SUMMARIZED < BEAT.railChatClick - 1500);
82− assert.equal(frameAt(BEAT.railChatClick - 1).words, SUMMARY_WORDS.length);
83− assert.equal(frameAt(BEAT.live - 1).live, false);
84− assert.equal(frameAt(BEAT.live).live, true);
86+ assert.ok(SUMMARIZED < BEAT.prClick - 700);
87+ assert.equal(frameAt(BEAT.prClick - 1).words, SUMMARY_WORDS.length);
88+ assert.ok(SUMMARY_WORDS.some((word) => word.includes("#431")));
8589 });
+47−52
44 * paused, resumed, jumped to a step, or shown still, and the server and
55 * the first client render agree (t = 0).
66 *
7− * The story is about the code. Priya's pull request #431 runs its checks,
8− * is approved by Dana and is merged by the queue, and production deploys.
9− * Then how it was made: the session Priya's coding agent recorded onto the
10− * pull request through g1t's MCP server. Then Priya comes back to Home:
11− * the notifications that settled themselves leave Where you're needed,
12− * Start here picks what is left, and What changed tells her in plain
13− * English what landed and that #431 is live. Last, the people: #web, where
14− * Dana reported the bug and hears it shipped.
7+ * The story is Priya's morning. She opens Home: the notifications that
8+ * settled overnight leave Where you're needed, Start here picks what is
9+ * left, and What changed tells her in plain English what landed, that it
10+ * is live, and that her pull request #431 is in review. She opens #431: it
11+ * runs its checks, is approved by Dana and is merged by the queue, and
12+ * production deploys. Then how it was made: the session Priya's coding
13+ * agent recorded onto the pull request through g1t's MCP server. Last, the
14+ * people: #web, where Dana reported the bug and hears it shipped.
1515 */
1616
17−export type Scene = "code" | "session" | "home" | "chat";
17+export type Scene = "home" | "code" | "session" | "chat";
1818
1919 /** Where the soft cursor points: an element marked `data-tour="<target>"`. */
20−export type CursorTarget = "pr-session" | "rail-code" | "rail-home" | "home-summarize" | "home-explain" | "rail-chat" | "idle";
20+export type CursorTarget = "home-summarize" | "home-pr" | "pr-session" | "rail-code" | "rail-chat" | "idle";
2121
2222 export type Frame = {
2323 scene: Scene;
4141 words: number;
4242 /** The summary is finished: Ask a follow-up and Summarize again show. */
4343 summarized: boolean;
44− /** #431 is live in production (Landed says so). */
45− live: boolean;
4644 /** Priya posted in #web that it shipped. */
4745 shipped: boolean;
4846 /** Dana says support has been told, in #web. */
7573 */
7674 export const SETTLED: { title: string; detail: string; why: string }[] = [
7775 { title: "Deploy failed on main", detail: "acme/web main · Production on an older build", why: "Production of web deployed at 3f9c2a1" },
78− { title: "CI failed on csv-export", detail: "acme/web csv-export · Run 412", why: "CI passed in run 413" },
76+ { title: "Preview failed on webhook-retries", detail: "acme/web #433 · Run 410", why: "Alex merged #433 after run 411 passed" },
7977 ];
8078
8179 /** What Changed's answer, as g1t streams it onto Home. */
8280 export const SUMMARY =
83− "Two changes landed in acme/web since you were last here. CSV exports now stream in batches of 1,000 rows (#431), so the largest accounts no longer time out; it went live 9 minutes after the merge queue merged it. Jordan pinned the build image's cache key, which fixed last night's failed deploy. Your webhook retries (#433) are ready to merge.";
81+ "Two changes landed in acme/web since you were last here, and both are live. Jordan pinned the build image's cache key, which fixed last night's failed deploy, and Alex merged webhook retries (#433). Your CSV export fix (#431) is up: rows now stream in batches of 1,000, so the largest accounts no longer time out. Dana is reviewing it.";
8482
8583 export const SUMMARY_WORDS = SUMMARY.split(" ");
8684
8785 /** The beats of the story, in ms from the start of the loop. */
8886 export const BEAT = {
89− code: 0,
90− check0: 900,
91− checkGap: 650,
92− diff: 1300,
93− approved: 3900,
94− merged: 5000,
95− sessionClick: 6300,
96− session: 6450,
97− entry0: 6700,
98− entryGap: 450,
99− railHomeClick: 9300,
100− home: 9450,
101− settle0: 10300,
87+ home: 0,
88+ settle0: 900,
10289 settleGap: 650,
10390 /** How long a settled row stays, saying why, before it leaves the list. */
10491 clearAfter: 1100,
105− summarizeClick: 12700,
106− summarizing: 12850,
107− word0: 13500,
92+ summarizeClick: 3600,
93+ summarizing: 3750,
94+ word0: 4400,
10895 wordGap: 48,
109− live: 14600,
110− railChatClick: 18200,
111− chat: 18350,
112− shipped: 19200,
113− told: 20400,
114− end: 23500,
96+ /** #431 in What changed's answer, pressed once the answer is in. */
97+ prClick: 8500,
98+ code: 8650,
99+ check0: 9550,
100+ checkGap: 650,
101+ diff: 9950,
102+ approved: 12550,
103+ merged: 13650,
104+ sessionClick: 14950,
105+ session: 15100,
106+ entry0: 15350,
107+ entryGap: 450,
108+ railChatClick: 18000,
109+ chat: 18150,
110+ shipped: 19000,
111+ told: 20200,
112+ end: 23300,
115113 } as const;
116114
117115 /** When the summary has finished streaming. */
122120
123121 /** The step pills under the frame: where each starts, for jumping to it. */
124122 export const PILLS: { scene: Scene; label: string; start: number }[] = [
125− { scene: "code", label: "Pull request", start: 0 },
123+ { scene: "home", label: "Home", start: BEAT.home },
124+ { scene: "code", label: "Pull request", start: BEAT.code },
126125 { scene: "session", label: "Session", start: BEAT.session },
127− { scene: "home", label: "Home", start: BEAT.home },
128126 { scene: "chat", label: "Chat", start: BEAT.chat },
129127 ];
130128
154152 function cursorAt(t: number): { cursor: CursorTarget; click: boolean } {
155153 const press = (at: number) => t >= at && t < at + 180;
156154 const moves: [number, CursorTarget][] = [
157− [BEAT.merged + 300, "pr-session"],
158− [BEAT.session + 1900, "rail-home"],
159− [BEAT.home + 400, "idle"],
160155 [BEAT.summarizeClick - 900, "home-summarize"],
161156 [BEAT.summarizing + 300, "idle"],
162− [SUMMARIZED + 300, "home-explain"],
157+ [SUMMARIZED + 300, "home-pr"],
158+ [BEAT.code + 400, "idle"],
159+ [BEAT.merged + 300, "pr-session"],
163160 [BEAT.railChatClick - 900, "rail-chat"],
164161 [BEAT.chat + 500, "idle"],
165162 ];
166163 let cursor: CursorTarget = "idle";
167164 for (const [at, target] of moves) if (t >= at) cursor = target;
168− const click = press(BEAT.sessionClick) || press(BEAT.railHomeClick) || press(BEAT.summarizeClick) || press(BEAT.railChatClick);
165+ const click = press(BEAT.summarizeClick) || press(BEAT.prClick) || press(BEAT.sessionClick) || press(BEAT.railChatClick);
169166 return { cursor, click };
170167 }
171168
172169 /** The frame at time t (ms into the loop). */
173170 export function frameAt(time: number): Frame {
174171 const t = ((time % LOOP_MS) + LOOP_MS) % LOOP_MS;
175− const scene: Scene = t >= BEAT.chat ? "chat" : t >= BEAT.home ? "home" : t >= BEAT.session ? "session" : "code";
172+ const scene: Scene = t >= BEAT.chat ? "chat" : t >= BEAT.session ? "session" : t >= BEAT.code ? "code" : "home";
176173 const { cursor, click } = cursorAt(t);
177− // Home is what Priya comes back to: nothing on it moves until she is there.
178− const onHome = t >= BEAT.home;
179174 return {
180175 scene,
181176 checks: count(t, BEAT.check0, BEAT.checkGap, CHECKS.length),
184179 merged: t >= BEAT.merged,
185180 // The session was recorded before the story opens; the tour plays it back once its tab is opened.
186181 entries: t < BEAT.session ? 0 : count(t, BEAT.entry0, BEAT.entryGap, SESSION.length),
187− settled: onHome ? count(t, BEAT.settle0, BEAT.settleGap, SETTLED.length) : 0,
188− cleared: onHome ? count(t, BEAT.settle0 + BEAT.clearAfter, BEAT.settleGap, SETTLED.length) : 0,
182+ settled: count(t, BEAT.settle0, BEAT.settleGap, SETTLED.length),
183+ cleared: count(t, BEAT.settle0 + BEAT.clearAfter, BEAT.settleGap, SETTLED.length),
189184 summarizing: t >= BEAT.summarizing,
190185 words: count(t, BEAT.word0, BEAT.wordGap, SUMMARY_WORDS.length),
191186 summarized: t >= SUMMARIZED,
192− live: t >= BEAT.live,
193187 shipped: t >= BEAT.shipped,
194188 told: t >= BEAT.told,
195189 cursor,
196190 click,
197− cursorShown: t >= BEAT.merged && t < BEAT.end - 600,
191+ // Out of the way while the pull request's checks run and it merges.
192+ cursorShown: (t >= BEAT.summarizeClick - 1500 && t < BEAT.code + 600) || (t >= BEAT.merged && t < BEAT.end - 600),
198193 };
199194 }
200195
201196 /** A still frame for each pill, for reduced motion: the moment the step makes its point. */
202197 export const STILLS: Record<Scene, number> = {
198+ home: SUMMARIZED + 400,
203199 code: BEAT.approved + 200,
204− session: BEAT.railHomeClick - 400,
205− home: SUMMARIZED + 400,
200+ session: BEAT.railChatClick - 400,
206201 chat: BEAT.told + 400,
207202 };
208203

Comments

Sign in to comment.