Commit

Mission control counts people's own changes as theirs; a shorter sidebar with setup under Settings, and a 56px top bar

The week splits by who wrote each change: agents on their own, agents merged by a person, and people. Landed without you is a share of the agents' changes only. Members and Teams move down beside Usage; Guardrails, Secrets and variables, Runners, Integrations and Webhooks move into Settings. The top bar and the docs' header are 56px, with a smaller mark.

syntaqxcommitted Parentfe52cb0Browse files
9 files+197−1020/9 viewed
+2−2
2626 flex-direction: column;
2727 height: 100%;
2828 }
29− /* The same bar as the app's: 4rem, and a line under it. */
29+ /* The same bar as the app's: 3.5rem, and a line under it. */
3030 .g1t-header-top {
3131 box-sizing: border-box;
32− height: 4rem;
32+ height: 3.5rem;
3333 flex-shrink: 0;
3434 padding: 0 var(--sl-nav-pad-x);
3535 border-bottom: 1px solid var(--g1t-line);
+1−1
3939 .g1t-title-lockup {
4040 display: inline-flex;
4141 align-items: baseline;
42− font-size: 1.5rem;
42+ font-size: 1.25rem;
4343 line-height: 1;
4444 }
4545 svg {
+3−3
3737 --sl-text-body: 0.9375rem;
3838 --sl-line-height: 1.75;
3939 --sl-content-width: 46rem;
40− /* The header's row, as tall as the app's top bar (4rem), and the tabs
40+ /* The header's row, as tall as the app's top bar (3.5rem), and the tabs
4141 beneath it. */
42− --sl-nav-height: 6.75rem;
42+ --sl-nav-height: 6.25rem;
4343 --sl-sidebar-width: 17rem;
4444
4545 color-scheme: dark;
6363 }
6464 /* On phones the menu button sits in the header's top row, not its middle. */
6565 .sl-menu-button {
66− top: calc((4rem - var(--sl-menu-button-size)) / 2) !important;
66+ top: calc((3.5rem - var(--sl-menu-button-size)) / 2) !important;
6767 }
6868 site-search button[data-open-modal] {
6969 border-radius: 0.5rem;
+2−2
395395 className="scroll-mt-28 rounded-xl border border-line"
396396 >
397397 <header
398− className={`sticky top-16 z-20 flex items-center gap-2.5 border-line bg-surface/95 px-3 py-2 backdrop-blur ${
398+ className={`sticky top-14 z-20 flex items-center gap-2.5 border-line bg-surface/95 px-3 py-2 backdrop-blur ${
399399 collapsed ? "rounded-xl" : "rounded-t-xl border-b"
400400 }`}
401401 >
707707 const allCollapsed = files.every((file) => collapsed.has(file.path));
708708 return (
709709 <div>
710− <div className="sticky top-16 z-30 -mx-1 mb-3 flex flex-wrap items-center gap-x-4 gap-y-2 bg-bg/90 px-1 py-2 backdrop-blur">
710+ <div className="sticky top-14 z-30 -mx-1 mb-3 flex flex-wrap items-center gap-x-4 gap-y-2 bg-bg/90 px-1 py-2 backdrop-blur">
711711 <span className="text-sm text-muted">
712712 <span className="font-medium text-fg">{files.length}</span> {files.length === 1 ? "file" : "files"}
713713 </span>
+31−18
425425
426426 /** Seven days of landed changes, each split into what agents landed alone and what a person merged. */
427427 function WeekChart({ week }: { week: Week }) {
428− const max = Math.max(1, ...week.days.map((d) => d.agents + d.people));
428+ const max = Math.max(1, ...week.days.map((d) => d.agents + d.assisted + d.people));
429429 const height = 112;
430430 const delta = change(week.total, week.previous);
431431 return (
450450 </div>
451451 {week.total === 0 ? (
452452 <p className="mt-4 rounded-lg border border-dashed border-line px-4 py-6 text-center text-sm leading-6 text-muted">
453− Nothing landed in the last 7 days. Each change that does shows here, by day, split by whether a person had to merge it.
453+ Nothing landed in the last 7 days. Each change that does shows here, by day: agents' changes that landed on their own, agents' that a person merged, and people's own.
454454 </p>
455455 ) : (
456456 <div className="relative mt-5" style={{ height: height + 20 }}>
457457 <div className="absolute inset-x-0 border-t border-line" style={{ top: height }} />
458458 <div className="absolute inset-x-0 top-0 flex items-end justify-between gap-1" style={{ height }}>
459459 {week.days.map((day, index) => {
460− const total = day.agents + day.people;
461− const agentsH = Math.round((day.agents / max) * (height - 4));
462− const peopleH = Math.round((day.people / max) * (height - 4));
460+ const total = day.agents + day.assisted + day.people;
461+ const bar = (n: number) => Math.max(3, Math.round((n / max) * (height - 4)));
462+ // Top to bottom: agents alone, agents with a person, people.
463+ const segments = [
464+ { n: day.agents, tone: "bg-merged" },
465+ { n: day.assisted, tone: "bg-warn" },
466+ { n: day.people, tone: "bg-info" },
467+ ].filter((segment) => segment.n > 0);
463468 const today = index === week.days.length - 1;
464469 return (
465470 <div key={day.key} className="group relative flex h-full flex-1 flex-col items-center justify-end">
474479 </span>
475480 )}
476481 <div className="flex w-full max-w-6 flex-col items-stretch gap-[2px]">
477− {day.people > 0 && <span className="block rounded-t bg-warn" style={{ height: Math.max(3, peopleH) }} />}
478− {day.agents > 0 && (
479− <span className={cn("block bg-merged", day.people > 0 ? "" : "rounded-t")} style={{ height: Math.max(3, agentsH) }} />
480− )}
482+ {segments.map((segment, at) => (
483+ <span key={segment.tone} className={cn("block", segment.tone, at === 0 && "rounded-t")} style={{ height: bar(segment.n) }} />
484+ ))}
481485 {total === 0 && <span className="block h-[2px] rounded-full bg-line-strong" />}
482486 </div>
483487 <div className="pointer-events-none absolute bottom-full left-1/2 z-10 mb-1 hidden -translate-x-1/2 rounded-md border border-line-strong bg-raised px-2.5 py-1.5 text-xs whitespace-nowrap shadow-lg shadow-black/40 group-hover:block">
484488 <p className="font-medium text-fg">{day.label}</p>
485489 <p className="mt-0.5 flex items-center gap-1.5 text-muted">
486− <span className="size-1.5 rounded-full bg-merged" /> {day.agents} by agents
490+ <span className="size-1.5 rounded-full bg-merged" /> {day.agents} by agents on their own
491+ </p>
492+ <p className="flex items-center gap-1.5 text-muted">
493+ <span className="size-1.5 rounded-full bg-warn" /> {day.assisted} by agents, merged by a person
487494 </p>
488495 <p className="flex items-center gap-1.5 text-muted">
489− <span className="size-1.5 rounded-full bg-warn" /> {day.people} needed a person
496+ <span className="size-1.5 rounded-full bg-info" /> {day.people} by people
490497 </p>
491498 </div>
492499 </div>
510517 )}
511518 <div className={cn("mt-3 flex-wrap gap-x-4 gap-y-1 text-xs text-muted", week.total === 0 ? "hidden" : "flex")}>
512519 <span className="inline-flex items-center gap-1.5">
513− <span className="size-2 rounded-sm bg-merged" /> Landed by agents
520+ <span className="size-2 rounded-sm bg-merged" /> Agents, on their own
521+ </span>
522+ <span className="inline-flex items-center gap-1.5">
523+ <span className="size-2 rounded-sm bg-warn" /> Agents, merged by a person
514524 </span>
515525 <span className="inline-flex items-center gap-1.5">
516− <span className="size-2 rounded-sm bg-warn" /> Needed a person
526+ <span className="size-2 rounded-sm bg-info" /> People
517527 </span>
518528 </div>
519529 <table className="sr-only">
521531 <thead>
522532 <tr>
523533 <th>Day</th>
524− <th>Landed by agents</th>
525− <th>Needed a person</th>
534+ <th>Agents, on their own</th>
535+ <th>Agents, merged by a person</th>
536+ <th>People</th>
526537 </tr>
527538 </thead>
528539 <tbody>
530541 <tr key={day.key}>
531542 <td>{day.key}</td>
532543 <td>{day.agents}</td>
544+ <td>{day.assisted}</td>
533545 <td>{day.people}</td>
534546 </tr>
535547 ))}
781793
782794 const rows = tab === "needs" ? sortRows(needs, sort) : tab === "waiting" ? sortRows(waiting, sort) : sortRows(landed, sort);
783795 const shown = all ? rows : rows.slice(0, ROWS);
784− const share = week.total > 0 ? week.byAgents / week.total : null;
796+ // Of the agents' own changes only: people's work is not theirs to land.
797+ const share = week.agentChanges > 0 ? week.byAgents / week.agentChanges : null;
785798 const delta = change(week.total, week.previous);
786799 const feed = everyActivity ? groups : groups.slice(0, 8);
787800
903916 label="Landed without you"
904917 dot="bg-merged"
905918 value={share == null ? "—" : `${Math.round(share * 100)}%`}
906− hint={share == null ? "nothing landed yet" : `${week.byAgents} of ${week.total}`}
907− title="Merged by g1t, by auto-merge or the merge queue, with no person pressing merge."
919+ hint={share == null ? "no agent changes yet" : `${week.byAgents} of ${week.agentChanges} agent changes`}
920+ title="Of the changes agents wrote, those g1t merged by auto-merge or the merge queue, with no person pressing merge. People's own changes are not counted."
908921 />
909922 <div className="col-span-2 lg:col-span-1">
910923 <Stat
+41−38
439439 }
440440
441441 /**
442− * A workspace's settings pages, which the sidebar drills into: what it
443− * pays, its repositories, tokens and record. Who belongs (Members, Teams)
444− * and what it builds and runs with (secrets, integrations, webhooks,
445− * guardrails) sit in the main list, for every member to see.
442+ * A workspace's settings pages, which the sidebar drills into: how it is
443+ * set up and connected (guardrails, secrets, runners, integrations,
444+ * webhooks), what it pays, its repositories, tokens and record. The main
445+ * list keeps the places work happens and who belongs; every member can
446+ * still open these.
446447 */
447−const SETTINGS_PAGE = /^\/([^/]+)\/-\/(settings|repositories|tokens|billing|audit)(\/|$)/;
448+const SETTINGS_PAGE =
449+ /^\/([^/]+)\/-\/(settings|repositories|tokens|guardrails|secrets|runners|integrations|webhooks|billing|audit)(\/|$)/;
448450 /** A project's settings pages, which the project's menu drills into. */
449451 const REPO_SETTINGS_PAGE = /^\/([^/]+)\/([^/-][^/]*)\/settings(\/|$)/;
450452
633635
634636 {ws && (
635637 <>
636− <Rule />
637− <div className="space-y-px">
638− {/* Who belongs, for every member to see; owners invite and manage there. */}
639− <SidebarLink to={`/${ws.slug}/-/people`} icon={<Users size={15} />}>
640− Members
641− </SidebarLink>
642− {roadmapIn("Workspace")
643− .filter((item) => item.key === "teams")
644− .map((item) => (
645− <SidebarSoonLink key={item.key} to={`/${ws.slug}/-/soon/${item.key}`} icon={WORKSPACE_ICONS[item.key]} about={item.summary}>
646− {item.title}
647− </SidebarSoonLink>
648− ))}
649− </div>
650638 <Rule />
651639 <div className="space-y-px">
652640 <SidebarLink to={`/${ws.slug}/-/agents`} icon={<Bot size={15} />}>
660648 </SidebarLink>
661649 <SidebarLink to={`/${ws.slug}/-/security`} icon={<ShieldCheck size={15} />}>
662650 Security
663− </SidebarLink>
664− <SidebarLink to={`/${ws.slug}/-/guardrails`} icon={<Gauge size={15} />}>
665− Guardrails
666− </SidebarLink>
667− <SidebarLink to={`/${ws.slug}/-/secrets`} icon={<Lock size={15} />}>
668− Secrets and variables
669− </SidebarLink>
670− <SidebarLink to={`/${ws.slug}/-/runners`} icon={<ServerCog size={15} />}>
671− Runners
672651 </SidebarLink>
673652 <SidebarLink to={`/${ws.slug}/-/packages`} icon={<Package size={15} />}>
674653 Packages
675654 </SidebarLink>
676− <SidebarLink to={`/${ws.slug}/-/integrations`} icon={<Plug size={15} />}>
677− Integrations
678− </SidebarLink>
679− <SidebarLink to={`/${ws.slug}/-/webhooks`} icon={<Webhook size={15} />}>
680− Webhooks
681− </SidebarLink>
682655 {roadmapIn("Workspace").filter((item) => item.key !== "teams").map((item) => (
683656 <SidebarSoonLink
684657 key={item.key}
693666
694667 <Rule />
695668 <div className="space-y-px">
669+ {/* Who belongs, for every member to see; owners invite and manage there. */}
670+ <SidebarLink to={`/${ws.slug}/-/people`} icon={<Users size={15} />}>
671+ Members
672+ </SidebarLink>
673+ {roadmapIn("Workspace")
674+ .filter((item) => item.key === "teams")
675+ .map((item) => (
676+ <SidebarSoonLink key={item.key} to={`/${ws.slug}/-/soon/${item.key}`} icon={WORKSPACE_ICONS[item.key]} about={item.summary}>
677+ {item.title}
678+ </SidebarSoonLink>
679+ ))}
696680 <SidebarLink to={`/${ws.slug}/-/usage`} icon={<BarChart3 size={15} />}>
697681 Usage
698682 </SidebarLink>
699683 <SidebarLink to="/support" icon={<LifeBuoy size={15} />}>
700684 Support
701685 </SidebarLink>
702− {/* Who belongs, what it pays and its record: a list of their own. */}
686+ {/* How it is set up and connected, what it pays and its record: a list of their own. */}
703687 <SidebarLink
704688 to={ws.role === "owner" ? `/${ws.slug}/-/settings` : `/${ws.slug}/-/repositories`}
705689 icon={<Settings size={15} />}
732716 Access tokens
733717 </SidebarLink>
734718 </div>
719+ <SidebarGroup title="Agents and runs" className="mt-3">
720+ <SidebarLink to={`/${slug}/-/guardrails`} icon={<Gauge size={15} />}>
721+ Guardrails
722+ </SidebarLink>
723+ <SidebarLink to={`/${slug}/-/secrets`} icon={<Lock size={15} />}>
724+ Secrets and variables
725+ </SidebarLink>
726+ <SidebarLink to={`/${slug}/-/runners`} icon={<ServerCog size={15} />}>
727+ Runners
728+ </SidebarLink>
729+ </SidebarGroup>
730+ <SidebarGroup title="Connections" className="mt-3">
731+ <SidebarLink to={`/${slug}/-/integrations`} icon={<Plug size={15} />}>
732+ Integrations
733+ </SidebarLink>
734+ <SidebarLink to={`/${slug}/-/webhooks`} icon={<Webhook size={15} />}>
735+ Webhooks
736+ </SidebarLink>
737+ </SidebarGroup>
735738 <Rule />
736739 <div className="space-y-px">
737740 <SidebarLink to={`/${slug}/-/billing`} icon={<CreditCard size={15} />}>
11411144 return (
11421145 <div className="flex h-full flex-col">
11431146 {/* The same height and rule as the top bar, so the two read as one line. */}
1144− <div className="flex h-16 shrink-0 items-center gap-1 border-b border-line pr-2 pl-2.5">
1147+ <div className="flex h-14 shrink-0 items-center gap-1 border-b border-line pr-2 pl-2.5">
11451148 {user ? (
11461149 <>
11471150 {/* The 1 alone beside the workspace: a square hover the height of the
11481151 switcher, the mark as tall as the workspace avatar. */}
11491152 <Link to="/" aria-label="g1t home" className="flex size-9 shrink-0 items-center justify-center rounded-md transition-colors hover:bg-raised">
1150− <Mark tight className="h-5 w-auto" />
1153+ <Mark tight className="h-4 w-auto" />
11511154 </Link>
11521155 <span className="shrink-0 text-line-strong" aria-hidden="true">
11531156 /
15201523 )}
15211524
15221525 <div className="flex min-h-screen min-w-0 flex-col lg:pl-64">
1523− <header className="sticky top-0 z-30 flex h-16 items-center gap-3 border-b border-line bg-bg/85 px-4 backdrop-blur sm:px-6">
1526+ <header className="sticky top-0 z-30 flex h-14 items-center gap-3 border-b border-line bg-bg/85 px-4 backdrop-blur sm:px-6">
15241527 <button
15251528 type="button"
15261529 aria-label="Open menu"
+42−12
160160 assert.ok(!isTestFile("src/contest.ts"));
161161 });
162162
163−const merged = (daysAgo: number, mergedBy: string | null, number = 1): Merged => ({
163+const merged = (daysAgo: number, mergedBy: string | null, number = 1, authoredByAgent = true): Merged => ({
164164 repo,
165165 number,
166166 title: `Change ${number}`,
167167 agent: "g1t",
168+ authoredByAgent,
168169 mergedBy,
169170 mergedAt: new Date(NOW - daysAgo * DAY).toISOString(),
170171 files: [],
177178 assert.ok(!landedByAgents({ mergedBy: "syntaqx" }));
178179 });
179180
180−test("the week is seven days, each split by who landed it, with the week before", () => {
181+test("the week is seven days, each split by who did the work, with the week before", () => {
181182 const week = weekOf(
182− [merged(0, "g1t"), merged(0, "syntaqx"), merged(1, "g1t"), merged(6, "g1t"), merged(8, "g1t"), merged(10, "alex"), merged(20, "g1t")],
183+ [
184+ merged(0, "g1t"),
185+ merged(0, "syntaqx"),
186+ // A person's own change, merged by them and auto-merged by g1t: both theirs.
187+ merged(0, "syntaqx", 2, false),
188+ merged(1, "g1t", 3, false),
189+ merged(1, "g1t"),
190+ merged(6, "g1t"),
191+ merged(8, "g1t"),
192+ merged(10, "alex"),
193+ merged(20, "g1t"),
194+ ],
183195 NOW,
184196 "UTC",
185197 );
187199 assert.equal(week.days[6].key, "2026-10-05");
188200 assert.equal(week.days[6].label, "Mon");
189201 assert.deepEqual(
190− week.days.map((d) => [d.agents, d.people]),
191− [[1, 0], [0, 0], [0, 0], [0, 0], [0, 0], [1, 0], [1, 1]],
202+ week.days.map((d) => [d.agents, d.assisted, d.people]),
203+ [[1, 0, 0], [0, 0, 0], [0, 0, 0], [0, 0, 0], [0, 0, 0], [1, 0, 1], [1, 1, 1]],
192204 );
193− assert.equal(week.total, 4);
205+ assert.equal(week.total, 6);
194206 assert.equal(week.byAgents, 3);
207+ assert.equal(week.agentChanges, 4);
208+ assert.equal(week.people, 2);
195209 assert.equal(week.previous, 2);
196210 // Not knowing the week before is not the same as nothing in it.
197211 assert.equal(weekOf([merged(0, "g1t")], NOW, "UTC", false).previous, null);
203217 assert.equal(dayKey(late, "America/Los_Angeles"), "2026-10-04");
204218 assert.equal(dayKey(late, "UTC"), "2026-10-05");
205219 assert.equal(dayKey(late, "Not/AZone"), "2026-10-05");
206− const week = weekOf([{ mergedAt: new Date(late).toISOString(), mergedBy: "g1t" }], NOW, "America/Los_Angeles");
220+ const week = weekOf([{ mergedAt: new Date(late).toISOString(), mergedBy: "g1t", authoredByAgent: true }], NOW, "America/Los_Angeles");
207221 assert.equal(week.days.find((d) => d.key === "2026-10-04")?.agents, 1);
208222 });
209223
315329 });
316330
317331 test("the summary says the week honestly", () => {
318− assert.equal(summaryLine({ total: 47, byAgents: 39, live: 2, needs: 8 }), "Agents landed 39 of 47 changes this week without you.");
319− assert.equal(summaryLine({ total: 3, byAgents: 3, live: 0, needs: 0 }), "Agents landed all 3 changes this week without you.");
320− assert.equal(summaryLine({ total: 2, byAgents: 0, live: 0, needs: 0 }), "2 changes landed this week, each merged by a person.");
321− assert.equal(summaryLine({ total: 0, byAgents: 0, live: 1, needs: 0 }), "1 agent is at work. Nothing has landed this week yet.");
322− assert.match(summaryLine({ total: 0, byAgents: 0, live: 0, needs: 0 }), /Assign an issue/);
332+ const none = { total: 0, byAgents: 0, agentChanges: 0, people: 0, live: 0, needs: 0 };
333+ assert.equal(
334+ summaryLine({ ...none, total: 47, byAgents: 39, agentChanges: 47 }),
335+ "Agents landed 39 of their 47 changes this week without you.",
336+ );
337+ assert.equal(
338+ summaryLine({ ...none, total: 3, byAgents: 3, agentChanges: 3 }),
339+ "Agents landed all 3 changes of theirs this week without you.",
340+ );
341+ assert.equal(
342+ summaryLine({ ...none, total: 2, agentChanges: 2 }),
343+ "Agents made 2 changes this week, each merged by a person.",
344+ );
345+ // People's own work is counted as theirs, never as agents' that needed help.
346+ assert.equal(
347+ summaryLine({ ...none, total: 7, byAgents: 4, agentChanges: 5, people: 2 }),
348+ "Agents landed 4 of their 5 changes this week without you, and people landed 2 changes of their own.",
349+ );
350+ assert.equal(summaryLine({ ...none, total: 3, people: 3 }), "People landed 3 changes this week; none were agents'.");
351+ assert.equal(summaryLine({ ...none, live: 1 }), "1 agent is at work. Nothing has landed this week yet.");
352+ assert.match(summaryLine(none), /Assign an issue/);
323353 });
+53−24
429429 number: number;
430430 title: string;
431431 agent: string;
432+ /** Whether an agent wrote it (its author is g1t or another agent), not a person. */
433+ authoredByAgent: boolean;
432434 mergedBy: string | null;
433435 mergedAt: string;
434436 files: ChangedFile[];
451453 }
452454 }
453455
454−export type WeekDay = { key: string; label: string; agents: number; people: number };
456+/**
457+ * One day's changes by who did the work: `agents`, written by an agent and
458+ * landed without a person; `assisted`, written by an agent and merged by a
459+ * person; `people`, written by a person, however it landed.
460+ */
461+export type WeekDay = { key: string; label: string; agents: number; assisted: number; people: number };
455462
456463 export type Week = {
457464 days: WeekDay[];
458− /** Changes landed in the last seven days, and how many without a person. */
465+ /** Changes landed in the last seven days. */
459466 total: number;
467+ /** Of those, agents' changes that landed without a person. */
460468 byAgents: number;
469+ /** Agents' changes, with a person or without. */
470+ agentChanges: number;
471+ /** People's own changes. */
472+ people: number;
461473 /** The seven days before, or null when the lists read do not reach back that far. */
462474 previous: number | null;
463475 };
464476
465477 /**
466− * The last seven days in the viewer's zone, oldest first, each split into
467− * what agents landed alone and what a person merged, and the week before
468− * as one number. `complete` says whether what was read reaches back two
469− * weeks; when it does not, the week before is not guessed.
478+ * The last seven days in the viewer's zone, oldest first, each split by who
479+ * did the work (see `WeekDay`), and the week before as one number.
480+ * `complete` says whether what was read reaches back two weeks; when it
481+ * does not, the week before is not guessed.
470482 */
471−export function weekOf(changes: Pick<Merged, "mergedAt" | "mergedBy">[], now: number, timeZone: string | null, complete = true): Week {
483+export function weekOf(
484+ changes: Pick<Merged, "mergedAt" | "mergedBy" | "authoredByAgent">[],
485+ now: number,
486+ timeZone: string | null,
487+ complete = true,
488+): Week {
472489 const days: WeekDay[] = [];
473490 const index = new Map<string, number>();
474491 for (let back = 6; back >= 0; back -= 1) {
482499 } catch {
483500 label = new Intl.DateTimeFormat("en-US", { weekday: "short", timeZone: "UTC" }).format(at);
484501 }
485− days.push({ key, label, agents: 0, people: 0 });
502+ days.push({ key, label, agents: 0, assisted: 0, people: 0 });
486503 }
487504 const oldest = days[0]?.key ?? "";
488505 const twoWeeks = dayKey(now - 13 * DAY, timeZone);
493510 const key = dayKey(at, timeZone);
494511 const slot = index.get(key);
495512 if (slot != null) {
496− if (landedByAgents(change)) days[slot].agents += 1;
497− else days[slot].people += 1;
513+ if (!change.authoredByAgent) days[slot].people += 1;
514+ else if (landedByAgents(change)) days[slot].agents += 1;
515+ else days[slot].assisted += 1;
498516 } else if (key < oldest && key >= twoWeeks) {
499517 previous += 1;
500518 }
501519 }
502− const byAgents = days.reduce((sum, day) => sum + day.agents, 0);
503− const total = byAgents + days.reduce((sum, day) => sum + day.people, 0);
504− return { days, total, byAgents, previous: complete ? previous : null };
520+ const sum = (pick: (day: WeekDay) => number) => days.reduce((total, day) => total + pick(day), 0);
521+ const byAgents = sum((day) => day.agents);
522+ const agentChanges = byAgents + sum((day) => day.assisted);
523+ const people = sum((day) => day.people);
524+ return { days, total: agentChanges + people, byAgents, agentChanges, people, previous: complete ? previous : null };
505525 }
506526
507527 /** The change from one number to another, as a share; null from nothing. */
553573 title: change.title,
554574 by: who(change.mergedBy ?? "g1t"),
555575 agent: change.agent,
556− byAgents: landedByAgents(change),
576+ byAgents: change.authoredByAgent && landedByAgents(change),
557577 at: Date.parse(change.mergedAt),
558578 to: `/${change.repo.namespace}/${change.repo.name}/pull/${change.number}`,
559579 facts: pullFacts({ checkStatus: "passed", files: change.files }).filter((fact) => fact.label !== "Required checks"),
582602
583603 // --- The summary ------------------------------------------------------------
584604
585−/** The sentence under the greeting: the week, honestly, in one line. */
586−export function summaryLine(input: { total: number; byAgents: number; live: number; needs: number }): string {
587− const { total, byAgents, live, needs } = input;
605+/**
606+ * The sentence under the greeting: the week, honestly, in one line. What
607+ * agents landed alone is counted against the agents' own changes, never
608+ * against what people wrote.
609+ */
610+export function summaryLine(input: {
611+ total: number;
612+ byAgents: number;
613+ agentChanges: number;
614+ people: number;
615+ live: number;
616+ needs: number;
617+}): string {
618+ const { total, byAgents, agentChanges, people, live, needs } = input;
588619 if (total > 0) {
589− const landed =
590− byAgents === total
591− ? `Agents landed all ${plural(total, "change")} this week without you.`
592− : byAgents === 0
593− ? `${plural(total, "change")} landed this week, each merged by a person.`
594− : `Agents landed ${byAgents} of ${plural(total, "change")} this week without you.`;
595− return landed;
620+ const theirs = people > 0 ? `, and people landed ${plural(people, "change")} of their own` : "";
621+ if (agentChanges === 0) return `People landed ${plural(people, "change")} this week; none were agents'.`;
622+ if (byAgents === agentChanges) return `Agents landed all ${plural(agentChanges, "change")} of theirs this week without you${theirs}.`;
623+ if (byAgents === 0) return `Agents made ${plural(agentChanges, "change")} this week, each merged by a person${theirs}.`;
624+ return `Agents landed ${byAgents} of their ${plural(agentChanges, "change")} this week without you${theirs}.`;
596625 }
597626 if (live > 0) return `${plural(live, "agent is", "agents are")} at work. Nothing has landed this week yet.`;
598627 if (needs > 0) return "Nothing has landed this week. What is waiting on you is below.";
+22−2
499499 const merged: Merged[] = (perRepo ?? []).flatMap(({ repo, closed }) =>
500500 (closed ?? []).flatMap((pull) =>
501501 pull.status === "merged" && pull.mergedAt
502− ? [{ repo: pathOf(repo), number: pull.number, title: pull.title, agent: pull.agent, mergedBy: pull.mergedBy, mergedAt: pull.mergedAt, files: pull.files }]
502+ ? [
503+ {
504+ repo: pathOf(repo),
505+ number: pull.number,
506+ title: pull.title,
507+ agent: pull.agent,
508+ // Who wrote it, not who merged it: a person's change that g1t
509+ // auto-merged is still theirs.
510+ authoredByAgent: pull.author.kind === "agent" || isAgent(pull.author.username),
511+ mergedBy: pull.mergedBy,
512+ mergedAt: pull.mergedAt,
513+ files: pull.files,
514+ },
515+ ]
503516 : [],
504517 ),
505518 );
582595 name: profile?.name?.trim() || username,
583596 greeting: greetingFor(hourIn(now, tz)),
584597 date: dateLine(now, tz),
585− summary: summaryLine({ total: week.total, byAgents: week.byAgents, live: liveRuns.length, needs: needRows.length }),
598+ summary: summaryLine({
599+ total: week.total,
600+ byAgents: week.byAgents,
601+ agentChanges: week.agentChanges,
602+ people: week.people,
603+ live: liveRuns.length,
604+ needs: needRows.length,
605+ }),
586606 workspace: slug,
587607 repos: repoList.filter((repo) => inWorkspace(repo)).map(pathOf),
588608 canRunAgents: models_ == null || models_.hosted || models_.own != null,