Skip to content

Commit

The home page leads with Code: the tour opens on the pull request, and the forge comes before Chat

The tour now opens on Otto's pull request #431 running its checks, being approved and merged by the queue; then #web, where it was asked for and where it shipped; then Otto's desk, the record of what he did; then the docs page. Pills, stills and the caption follow. The desk shows Shipped once the pull request has merged. The four modes start with Code, and the Code and forge sections come before Chat.

syntaqxcommitted Parent89cec7fBrowse files
4 files+198−2490/4 viewed
+116−116
163163 /** The four modes of a workspace. */
164164 const MODES: { icon: ReactNode; name: string; about: string; soon?: boolean; to: string }[] = [
165165 {
166+ icon: <Code2 size={18} />,
167+ name: "Code",
168+ about: "Repositories, issues, pull requests, checks, a merge queue and deployments. For the people who build.",
169+ to: `${DOCS}/concepts/overview/`,
170+ },
171+ {
166172 icon: <MessagesSquare size={18} />,
167173 name: "Chat",
168174 about: "Channels, direct messages and threads, live. People and agents are members alike.",
181187 soon: true,
182188 to: `${DOCS}/guides/docs/`,
183189 },
184− {
185− icon: <Code2 size={18} />,
186− name: "Code",
187− about: "Repositories, issues, pull requests, checks, a merge queue and deployments. For the people who build.",
188− to: `${DOCS}/concepts/overview/`,
189− },
190190 ];
191191
192192 /** Who in a company talks to the agents, and what they ask. */
433433 </div>
434434 </section>
435435
436− {/* Chat first. */}
437− <section className="mx-auto grid max-w-6xl items-start gap-12 px-4 pt-24 lg:grid-cols-[1.1fr_1fr] lg:gap-16">
436+ {/* Code. */}
437+ <section className="mx-auto max-w-6xl px-4 pt-24">
438+ <div className="max-w-3xl">
439+ <Eyebrow>Code</Eyebrow>
440+ <h2 className="mt-3 text-3xl font-semibold tracking-tight text-balance sm:text-5xl">
441+ A forge built for many agents at once.
442+ </h2>
443+ <p className="mt-5 max-w-2xl text-lg leading-8 text-muted">
444+ Underneath the conversation is plain git. Every change an agent makes is a pull request in its own fork,
445+ checked by g1t, reviewed, and landed through a queue that keeps main passing.
446+ </p>
447+ </div>
448+
449+ <div className="mt-6 divide-y divide-line">
450+ <Pillar
451+ eyebrow="Outcomes"
452+ title="Hand off an outcome, not just a task"
453+ art={<PlanArt className="w-full" />}
454+ points={[
455+ "A brief planned into issues with dependencies",
456+ "Your workflows' required checks on every change",
457+ "Work starts as each dependency lands",
458+ "The plan as a live graph, with its cost",
459+ ]}
460+ more={["Hand off an outcome", `${DOCS}/guides/outcomes/`]}
461+ >
462+ Write what should be true. A planner turns it into issues, each saying what done looks like, and the order
463+ they depend on. Agents take each issue as it unblocks, and you watch the whole outcome converge.
464+ </Pillar>
465+
466+ <Pillar
467+ flip
468+ eyebrow="Ship safely"
469+ title="Main only moves to what passed"
470+ art={<QueueArt className="w-full" />}
471+ points={[
472+ "Checks run by g1t in a clean sandbox",
473+ "Workflows from .g1t/workflows",
474+ "A merge queue that tests changes together",
475+ "Conflicts found on every push, before a merge",
476+ "Catch up with main in seconds",
477+ "Reviews by people and by agents",
478+ ]}
479+ more={["The merge queue", `${DOCS}/guides/merge-queue/`]}
480+ >
481+ Checks are run by g1t, never by the agent being checked. The queue tests each change together with what
482+ lands ahead of it; one that breaks goes back to its author with what failed, and main never sees it.
483+ </Pillar>
484+
485+ <Pillar
486+ eyebrow="Context"
487+ title="Every line knows why it is there"
488+ art={<WhyArt className="w-full" />}
489+ points={[
490+ "Sessions recorded onto pull requests",
491+ "Why-blame on any line",
492+ "A context hub agents search before they start",
493+ "Search across code, issues and people",
494+ ]}
495+ more={["Sessions and why-blame", `${DOCS}/guides/why-blame/`]}
496+ >
497+ Pick any line. g1t shows the commit that changed it, the pull request and issue it came from, and the
498+ agent&apos;s own session: what it read, ran and decided. The reasoning stays with the code, for people and
499+ for the next agent.
500+ </Pillar>
501+
502+ <Pillar
503+ flip
504+ eyebrow="Run it"
505+ title="Every change, live on the edge"
506+ art={<DeployArt className="w-full" />}
507+ points={[
508+ "A live preview for every pull request",
509+ "Production on every merge to main",
510+ "Custom domains, with certificates",
511+ "Projects that depend on each other",
512+ ]}
513+ more={["Deployments", `${DOCS}/guides/deployments/`]}
514+ >
515+ Turn on deployments and every pull request gets its own address on g1t.page; merging ships production.
516+ Reviewers, and the agents reviewing for you, click through a change instead of reading a diff. An app nobody
517+ visits runs nothing and costs nothing.
518+ </Pillar>
519+ </div>
520+ </section>
521+
522+ {/* The forge, for people. */}
523+ <section className="mx-auto max-w-6xl px-4 py-24">
524+ <div className="grid gap-10 lg:grid-cols-[1fr_1.2fr] lg:gap-16">
525+ <div>
526+ <Eyebrow>Collaborate</Eyebrow>
527+ <h2 className="mt-3 text-3xl font-semibold tracking-tight text-balance">
528+ A complete forge for the people who build
529+ </h2>
530+ <p className="mt-4 max-w-md leading-7 text-muted">
531+ Issues, branches, pull requests and reviews, the way your team already works, with agents as members
532+ alongside you. Work by hand, hand work off, or both on the same issue.
533+ </p>
534+ <More to={`${DOCS}/concepts/overview/`}>How g1t works</More>
535+ </div>
536+ <div className="rounded-3xl bg-surface p-7 ring-1 ring-line">
537+ <p className="text-sm font-medium">Included in every workspace</p>
538+ <Tags items={FORGE} />
539+ </div>
540+ </div>
541+ </section>
542+
543+ {/* Chat, where work is asked for. */}
544+ <section className="mx-auto grid max-w-6xl items-start gap-12 px-4 lg:grid-cols-[1.1fr_1fr] lg:gap-16">
438545 <div>
439546 <Eyebrow>Chat</Eyebrow>
440547 <h2 className="mt-3 text-3xl font-semibold tracking-tight text-balance sm:text-4xl">
737844 </div>
738845 </section>
739846
740− {/* Code. */}
741− <section className="mx-auto max-w-6xl px-4 pt-24">
742− <div className="max-w-3xl">
743− <Eyebrow>Code</Eyebrow>
744− <h2 className="mt-3 text-3xl font-semibold tracking-tight text-balance sm:text-5xl">
745− A forge built for many agents at once.
746− </h2>
747− <p className="mt-5 max-w-2xl text-lg leading-8 text-muted">
748− Underneath the conversation is plain git. Every change an agent makes is a pull request in its own fork,
749− checked by g1t, reviewed, and landed through a queue that keeps main passing.
750− </p>
751− </div>
752−
753− <div className="mt-6 divide-y divide-line">
754− <Pillar
755− eyebrow="Outcomes"
756− title="Hand off an outcome, not just a task"
757− art={<PlanArt className="w-full" />}
758− points={[
759− "A brief planned into issues with dependencies",
760− "Your workflows' required checks on every change",
761− "Work starts as each dependency lands",
762− "The plan as a live graph, with its cost",
763− ]}
764− more={["Hand off an outcome", `${DOCS}/guides/outcomes/`]}
765− >
766− Write what should be true. A planner turns it into issues, each saying what done looks like, and the order
767− they depend on. Agents take each issue as it unblocks, and you watch the whole outcome converge.
768− </Pillar>
769−
770− <Pillar
771− flip
772− eyebrow="Ship safely"
773− title="Main only moves to what passed"
774− art={<QueueArt className="w-full" />}
775− points={[
776− "Checks run by g1t in a clean sandbox",
777− "Workflows from .g1t/workflows",
778− "A merge queue that tests changes together",
779− "Conflicts found on every push, before a merge",
780− "Catch up with main in seconds",
781− "Reviews by people and by agents",
782− ]}
783− more={["The merge queue", `${DOCS}/guides/merge-queue/`]}
784− >
785− Checks are run by g1t, never by the agent being checked. The queue tests each change together with what
786− lands ahead of it; one that breaks goes back to its author with what failed, and main never sees it.
787− </Pillar>
788−
789− <Pillar
790− eyebrow="Context"
791− title="Every line knows why it is there"
792− art={<WhyArt className="w-full" />}
793− points={[
794− "Sessions recorded onto pull requests",
795− "Why-blame on any line",
796− "A context hub agents search before they start",
797− "Search across code, issues and people",
798− ]}
799− more={["Sessions and why-blame", `${DOCS}/guides/why-blame/`]}
800− >
801− Pick any line. g1t shows the commit that changed it, the pull request and issue it came from, and the
802− agent&apos;s own session: what it read, ran and decided. The reasoning stays with the code, for people and
803− for the next agent.
804− </Pillar>
805−
806− <Pillar
807− flip
808− eyebrow="Run it"
809− title="Every change, live on the edge"
810− art={<DeployArt className="w-full" />}
811− points={[
812− "A live preview for every pull request",
813− "Production on every merge to main",
814− "Custom domains, with certificates",
815− "Projects that depend on each other",
816− ]}
817− more={["Deployments", `${DOCS}/guides/deployments/`]}
818− >
819− Turn on deployments and every pull request gets its own address on g1t.page; merging ships production.
820− Reviewers, and the agents reviewing for you, click through a change instead of reading a diff. An app nobody
821− visits runs nothing and costs nothing.
822− </Pillar>
823− </div>
824− </section>
825−
826− {/* The forge, for people. */}
847+ {/* Secure by default */}
827848 <section className="mx-auto max-w-6xl px-4 py-24">
828− <div className="grid gap-10 lg:grid-cols-[1fr_1.2fr] lg:gap-16">
829− <div>
830− <Eyebrow>Collaborate</Eyebrow>
831− <h2 className="mt-3 text-3xl font-semibold tracking-tight text-balance">
832− A complete forge for the people who build
833− </h2>
834− <p className="mt-4 max-w-md leading-7 text-muted">
835− Issues, branches, pull requests and reviews, the way your team already works, with agents as members
836− alongside you. Work by hand, hand work off, or both on the same issue.
837− </p>
838− <More to={`${DOCS}/concepts/overview/`}>How g1t works</More>
839− </div>
840− <div className="rounded-3xl bg-surface p-7 ring-1 ring-line">
841− <p className="text-sm font-medium">Included in every workspace</p>
842− <Tags items={FORGE} />
843− </div>
844− </div>
845− </section>
846−
847− {/* Secure by default */}
848− <section className="mx-auto max-w-6xl px-4 pb-24">
849849 <Eyebrow>Secure by default</Eyebrow>
850850 <h2 className="mt-3 max-w-2xl text-3xl font-semibold tracking-tight text-balance">
851851 Safe to hand the work to, and healthy without anyone watching
+16−12
158158
159159 /** The pill a frame belongs to, from what it shows (the clock's time is not React state). */
160160 function pillOf(frame: Frame): number {
161− if (frame.scene === "docs") return 3;
162− if (frame.scene === "code" || frame.merged) return 2;
163− if (frame.scene === "agents") return 1;
164− return 0;
161+ return Math.max(0, PILLS.findIndex((pill) => pill.scene === frame.scene));
165162 }
166163
167164 /** What the frame's top bar says about the step: shipped today, or a preview. */
628625 </p>
629626 <p className="text-xs text-muted">@otto · Software Engineer, Engineering</p>
630627 </div>
631− <span className="ml-auto flex items-center gap-1.5 rounded-full bg-warn/10 px-2.5 py-1 text-[11px] text-warn">
632− <span className="tour-pulse size-1.5 rounded-full bg-warn" />
633− Working
634− </span>
628+ {frame.merged ? (
629+ <span className="ml-auto flex items-center gap-1.5 rounded-full bg-merged/10 px-2.5 py-1 text-[11px] text-merged">
630+ <GitMerge size={11} />
631+ Shipped #431
632+ </span>
633+ ) : (
634+ <span className="ml-auto flex items-center gap-1.5 rounded-full bg-warn/10 px-2.5 py-1 text-[11px] text-warn">
635+ <span className="tour-pulse size-1.5 rounded-full bg-warn" />
636+ Working
637+ </span>
638+ )}
635639 </div>
636640 <div className="flex gap-5 border-b border-line px-5 text-[12px]">
637641 {["Desk", "Profile", "Spend", "Activity"].map((tab) => (
10191023 observer.observe(element);
10201024 return () => observer.disconnect();
10211025 }, []);
1022− const scenes: Scene[] = ["chat", "agents", "code", "docs"];
1026+ const scenes: Scene[] = ["code", "chat", "agents", "docs"];
10231027 const mains: Record<Scene, ReactNode> = {
10241028 chat: <ChatMain frame={frame} />,
10251029 agents: <AgentsMain frame={frame} />,
10751079
10761080 function Phone({ frame }: { frame: Frame }) {
10771081 const note = honesty(frame);
1078− const scenes: Scene[] = ["chat", "agents", "code", "docs"];
1082+ const scenes: Scene[] = ["code", "chat", "agents", "docs"];
10791083 return (
10801084 <div className="relative h-[34rem] w-full overflow-hidden rounded-2xl bg-surface text-left shadow-2xl shadow-black/50 ring-1 ring-line">
10811085 <div className="flex h-12 items-center gap-2 border-b border-line px-3">
11941198 <p className="flex items-center gap-2 text-sm font-semibold text-fg">
11951199 Otto <AgentTag />
11961200 </p>
1197− <p className="text-[11px] text-muted">Working · ${cost.toFixed(2)} of $5</p>
1201+ <p className="text-[11px] text-muted">{frame.merged ? "Shipped #431" : "Working"} · ${cost.toFixed(2)} of $5</p>
11981202 </div>
11991203 </div>
12001204 <div className="rounded-xl bg-bg ring-1 ring-line">
13821386 })}
13831387 </div>
13841388 <p className="mx-auto mt-3 max-w-xl text-center text-xs leading-5 text-faint text-balance">
1385− A preview of where g1t is going. Chat, agents you DM, pull requests, checks and the merge queue work today.
1389+ A preview of where g1t is going. Pull requests, checks, the merge queue, chat and agents you DM work today.
13861390 Handoffs and consults between agents, live desks and Docs are coming soon.
13871391 </p>
13881392 </div>
+24−33
11 import assert from "node:assert/strict";
22 import { test } from "node:test";
33
4−import { ASK, BEAT, CHECKS, LOOP_MS, PILLS, STEPS, STILLS, frameAt, pillAt, pillProgress, sameFrame } from "./tour.ts";
4+import { BEAT, CHECKS, LOOP_MS, PILLS, STEPS, STILLS, frameAt, pillAt, pillProgress, sameFrame } from "./tour.ts";
55
6−test("the first frame is the empty channel, the same on the server and the client", () => {
6+test("the first frame is the pull request before its checks, the same on the server and the client", () => {
77 const frame = frameAt(0);
8− assert.equal(frame.scene, "chat");
9− assert.equal(frame.typed, 0);
10− assert.equal(frame.sent, false);
8+ assert.equal(frame.scene, "code");
9+ assert.equal(frame.checks, 0);
10+ assert.equal(frame.merged, false);
1111 assert.equal(frame.cursorShown, false);
1212 assert.ok(sameFrame(frame, frameAt(0)));
1313 });
1414
15−test("the message is typed in full before it is sent, then the composer empties", () => {
16− assert.equal(frameAt(BEAT.typed).typed, ASK.length);
17− assert.equal(frameAt(BEAT.typed - 1).typed, ASK.length - 1);
18− const sent = frameAt(BEAT.sent);
19− assert.equal(sent.sent, true);
20− assert.equal(sent.typed, 0);
21−});
22−
23−test("typing only ever moves forward", () => {
24− let last = 0;
25− for (let t = 0; t <= BEAT.typed; t += 17) {
26− const typed = frameAt(t).typed;
27− assert.ok(typed >= last);
28− last = typed;
29− }
30−});
31−
32−test("the scenes come in the story's order", () => {
15+test("the scenes come in the story's order: Code first", () => {
3316 const order: string[] = [];
3417 for (let t = 0; t < LOOP_MS; t += 100) {
3518 const scene = frameAt(t).scene;
3619 if (order[order.length - 1] !== scene) order.push(scene);
3720 }
38− assert.deepEqual(order, ["chat", "agents", "code", "chat", "docs"]);
21+ assert.deepEqual(order, ["code", "chat", "agents", "docs"]);
3922 });
4023
41−test("steps and checks finish before the cursor leaves them", () => {
42− assert.equal(frameAt(BEAT.pullClick).steps, STEPS.length);
24+test("checks pass before review, and the queue merges only after approval", () => {
4325 assert.equal(frameAt(BEAT.approved).checks, CHECKS.length);
44− assert.equal(frameAt(BEAT.pullClick).cursor, "task-pull");
26+ assert.equal(frameAt(BEAT.merged - 1).merged, false);
27+ assert.equal(frameAt(BEAT.merged).approved, true);
28+});
29+
30+test("the cursor leaves the code only once it has merged", () => {
31+ assert.equal(frameAt(BEAT.merged - 1).cursorShown, false);
32+ assert.equal(frameAt(BEAT.railChatClick).cursor, "rail-chat");
33+ assert.equal(frameAt(BEAT.railChatClick).click, true);
34+});
35+
36+test("Otto's work is done before the story opens", () => {
37+ for (const t of [0, BEAT.chat, BEAT.agents]) assert.equal(frameAt(t).steps, STEPS.length);
4538 });
4639
4740 test("the loop wraps", () => {
5043 });
5144
5245 test("pills start where their scene does, and progress runs 0 to 1", () => {
46+ assert.equal(PILLS[0].scene, "code");
5347 PILLS.forEach((pill, index) => {
5448 assert.equal(frameAt(pill.start + 1).scene, pill.scene);
5549 assert.equal(pillAt(pill.start + 1), index);
5650 });
57− assert.equal(pillAt(BEAT.chatAgain + 10), 2);
5851 assert.ok(pillProgress(PILLS[1].start) < 0.01);
5952 assert.ok(pillProgress(PILLS[2].start - 1) > 0.99);
6053 });
6154
6255 test("each still shows its step's point", () => {
63− assert.equal(frameAt(STILLS.chat).handoff, true);
64− assert.equal(frameAt(STILLS.agents).steps, STEPS.length);
6556 assert.equal(frameAt(STILLS.code).approved, true);
57+ assert.equal(frameAt(STILLS.chat).izzy, true);
58+ assert.equal(frameAt(STILLS.agents).scene, "agents");
6659 assert.equal(frameAt(STILLS.docs).docUpdated, true);
6760 });
6861
69−test("Izzy tells #support after g1t says it shipped, and the consult shows while Otto works", () => {
62+test("Izzy tells #support after g1t says it shipped", () => {
7063 assert.equal(frameAt(BEAT.izzy - 1).izzy, false);
7164 assert.equal(frameAt(BEAT.izzy).shipped, true);
72− assert.equal(frameAt(BEAT.agents).consult, false);
73− assert.equal(frameAt(BEAT.pullClick).consult, true);
7465 });
+42−88
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: a person asks g1t in #web to fix the CSV export; g1t hands it
8− * to @otto and the review to @margo; Otto's desk works it, consulting
9− * Margo on the way; the pull request goes green and
10− * merges; g1t says it shipped and Izzy tells #support; Inky updates the
11− * docs page.
7+ * The story opens on the code: Otto's pull request #431 runs its checks,
8+ * is approved and is merged by the queue. Then where it came from: the
9+ * ask in #web, handed to @otto by g1t, now merged, and Izzy telling
10+ * #support. Then Otto's desk, the record of what he did and spent, and
11+ * last Inky's docs page picking up the change.
1212 */
1313
1414 export type Scene = "chat" | "agents" | "code" | "docs";
7373 ];
7474
7575 export const CHECKS = ["build", "test", "lint", "preview"];
76−
77−/** When the person starts typing, and how long each character takes, in ms. */
78−const TYPE_START = 1500;
7976
80−/**
81− * A human typing pace: about 45 ms a character, a little longer after a
82− * space, and a short hesitation after punctuation. Seeded, so every loop
83− * and every render types the same way.
84− */
85−function typingTimes(text: string): number[] {
86− let seed = 7;
87− const random = () => {
88− seed = (seed * 16807) % 2147483647;
89− return (seed - 1) / 2147483646;
90− };
91− const times: number[] = [];
92− let at = TYPE_START;
93− for (const char of text) {
94− at += 32 + random() * 34;
95− if (char === " ") at += random() * 30;
96− times.push(Math.round(at));
97− if (char === "." || char === "?" || char === ",") at += 220 + random() * 160;
98− }
99− return times;
100−}
101−
102−const TYPED_AT = typingTimes(ASK);
103−const TYPED_END = TYPED_AT[TYPED_AT.length - 1];
104−
10577 /** The beats of the story, in ms from the start of the loop. */
10678 export const BEAT = {
107− composerClick: 1100,
108− typed: TYPED_END,
109− sendClick: TYPED_END + 700,
110− sent: TYPED_END + 800,
111− g1tTyping: TYPED_END + 1300,
112− handoff: TYPED_END + 2500,
113− railAgentsClick: TYPED_END + 4300,
114− agents: TYPED_END + 4450,
115− step0: TYPED_END + 5400,
116− stepGap: 1150,
117− pullClick: TYPED_END + 11900,
118− code: TYPED_END + 12050,
119− check0: TYPED_END + 12900,
79+ code: 0,
80+ check0: 900,
12081 checkGap: 650,
121− diff: TYPED_END + 13300,
122− approved: TYPED_END + 15900,
123− merged: TYPED_END + 17000,
124− railChatClick: TYPED_END + 18300,
125− chatAgain: TYPED_END + 18450,
126− cardMerged: TYPED_END + 19100,
127− shipped: TYPED_END + 20400,
128− izzy: TYPED_END + 21600,
129− railDocsClick: TYPED_END + 23000,
130− docs: TYPED_END + 23150,
131− docUpdated: TYPED_END + 24300,
132− end: TYPED_END + 29000,
82+ diff: 1300,
83+ approved: 3900,
84+ merged: 5000,
85+ railChatClick: 6300,
86+ chat: 6450,
87+ cardMerged: 7100,
88+ shipped: 8400,
89+ izzy: 9600,
90+ railAgentsClick: 11000,
91+ agents: 11150,
92+ railDocsClick: 15000,
93+ docs: 15150,
94+ docUpdated: 16300,
95+ end: 21000,
13396 } as const;
13497
13598 /** The whole loop, in ms. */
137100
138101 /** The step pills under the frame: where each starts, for jumping to it. */
139102 export const PILLS: { scene: Scene; label: string; start: number; soon: boolean }[] = [
140− { scene: "chat", label: "Chat", start: 0, soon: false },
103+ { scene: "code", label: "Code", start: 0, soon: false },
104+ { scene: "chat", label: "Chat", start: BEAT.chat, soon: false },
141105 { scene: "agents", label: "Agents", start: BEAT.agents, soon: true },
142− { scene: "code", label: "Code", start: BEAT.code, soon: false },
143106 { scene: "docs", label: "Docs", start: BEAT.docs, soon: true },
144107 ];
145108
146−/** Which pill a time falls under. Back in chat after the merge is still Code's story. */
109+/** Which pill a time falls under. */
147110 export function pillAt(t: number): number {
148111 let index = 0;
149112 PILLS.forEach((pill, i) => {
169132 function cursorAt(t: number): { cursor: CursorTarget; click: boolean } {
170133 const press = (at: number) => t >= at && t < at + 180;
171134 const moves: [number, CursorTarget][] = [
172− [300, "composer"],
173− [TYPED_END + 150, "send"],
174− [BEAT.handoff + 700, "rail-agents"],
175− [BEAT.step0 + 4 * BEAT.stepGap + 500, "task-pull"],
176135 [BEAT.merged + 300, "rail-chat"],
177− [BEAT.izzy + 500, "rail-docs"],
136+ [BEAT.izzy + 500, "rail-agents"],
137+ [BEAT.agents + 2600, "rail-docs"],
178138 [BEAT.docs + 500, "idle"],
179139 ];
180140 let cursor: CursorTarget = "idle";
181141 for (const [at, target] of moves) if (t >= at) cursor = target;
182− const click =
183− press(BEAT.composerClick) ||
184− press(BEAT.sendClick) ||
185− press(BEAT.railAgentsClick) ||
186− press(BEAT.pullClick) ||
187− press(BEAT.railChatClick) ||
188− press(BEAT.railDocsClick);
142+ const click = press(BEAT.railChatClick) || press(BEAT.railAgentsClick) || press(BEAT.railDocsClick);
189143 return { cursor, click };
190144 }
191145
192−/** The frame at time t (ms into the loop). */
146+/**
147+ * The frame at time t (ms into the loop). The ask, g1t's handoff and
148+ * Otto's work all happened before the pull request the story opens on, so
149+ * they are shown done throughout.
150+ */
193151 export function frameAt(time: number): Frame {
194152 const t = ((time % LOOP_MS) + LOOP_MS) % LOOP_MS;
195− const scene: Scene =
196− t >= BEAT.docs ? "docs" : t >= BEAT.chatAgain ? "chat" : t >= BEAT.code ? "code" : t >= BEAT.agents ? "agents" : "chat";
197− let typed = 0;
198− while (typed < TYPED_AT.length && TYPED_AT[typed] <= t) typed++;
199− const sent = t >= BEAT.sent;
153+ const scene: Scene = t >= BEAT.docs ? "docs" : t >= BEAT.agents ? "agents" : t >= BEAT.chat ? "chat" : "code";
200154 const { cursor, click } = cursorAt(t);
201155 return {
202156 scene,
203− typed: sent ? 0 : typed,
204− sent,
205− g1tTyping: t >= BEAT.g1tTyping && t < BEAT.handoff,
206− handoff: t >= BEAT.handoff,
207− steps: count(t, BEAT.step0, BEAT.stepGap, STEPS.length),
157+ typed: 0,
158+ sent: true,
159+ g1tTyping: false,
160+ handoff: true,
161+ steps: STEPS.length,
208162 checks: count(t, BEAT.check0, BEAT.checkGap, CHECKS.length),
209163 diff: t >= BEAT.diff,
210164 approved: t >= BEAT.approved,
212166 cardMerged: t >= BEAT.cardMerged,
213167 shipped: t >= BEAT.shipped,
214168 izzy: t >= BEAT.izzy,
215− consult: t >= BEAT.step0 + 2 * BEAT.stepGap + 500,
169+ consult: true,
216170 docUpdated: t >= BEAT.docUpdated,
217171 cursor,
218172 click,
219− cursorShown: t >= 200 && t < BEAT.end - 600,
173+ cursorShown: t >= BEAT.merged && t < BEAT.end - 600,
220174 };
221175 }
222176
223177 /** A still frame for each pill, for reduced motion: the moment the step makes its point. */
224178 export const STILLS: Record<Scene, number> = {
225− chat: BEAT.handoff + 400,
226− agents: BEAT.step0 + 4 * BEAT.stepGap + 200,
227179 code: BEAT.approved + 200,
180+ chat: BEAT.izzy + 400,
181+ agents: BEAT.agents + 400,
228182 docs: BEAT.docUpdated + 400,
229183 };
230184