flagon-io/g1t

public

Git for AI scale: a forge for thousands of agents working on the same code at once.

Commit

Pull requests: title and tabs across the full width, with counts and the size of the change

syntaqxcommitted Parent30c6956Browse files
4 files+611−5880/4 viewed
+1−1
146146 ## Review
147147
148148 Anyone who can see a pull request can comment on it, on the whole of it or
149−on a single line of its change. Line comments are shown in the **Changes**
149+on a single line of its change. Line comments are shown in the **Files changed**
150150 tab under the line they are about.
151151
152152 You can also ask a **g1t agent** to review. It reads the change in a sandbox
+1−1
6969
7070 Everything it reads, runs and decides is recorded in the pull request's
7171 **Session** as it happens; see [sessions and why-blame](/guides/why-blame/).
72−The **Changes** tab shows the resulting diff.
72+The **Files changed** tab shows the resulting diff.
7373
7474 If an agent fails, or finishes without changing anything, its pull request
7575 is closed and its session says why.
+1−1
5858 When a g1t agent's pull request is ready, review it the way you would
5959 anyone's:
6060
61−1. Open the **Changes** tab and comment on the lines you want changed.
61+1. Open the **Files changed** tab and comment on the lines you want changed.
6262 2. Submit a review with **Request changes**, saying what you want.
6363
6464 The agent is sent back with your review, your comments on lines included.
+608−585
258258 return result.ok ? null : { error: result.error.message, action };
259259 }
260260
261+/** How many, beside a tab's name, when there are any. */
262+function TabCount({ n }: { n: number }) {
263+ if (n <= 0) return null;
264+ return (
265+ <span className="rounded-full bg-raised px-1.5 py-px text-xs tabular-nums text-muted ring-1 ring-line">
266+ {n.toLocaleString("en-US")}
267+ </span>
268+ );
269+}
270+
261271 function TabLink({
262272 to,
263273 active,
481491
482492 return (
483493 // The changes get the whole width; people and settings are a tab away.
484− <div className={`grid gap-8 ${tab === "changes" ? "" : "lg:grid-cols-[1fr_19rem]"}`}>
485− <div className="min-w-0">
494+ <div>
495+ {/* The title and the tabs run the full width; what is happening on
496+ the pull request sits at the top of Conversation. */}
497+ <header>
486498 <h2 className="text-2xl font-semibold tracking-tight text-balance">
487499 {pull.title} <span className="font-normal text-faint">#{pull.number}</span>
488500 </h2>
535547 </p>
536548 )}
537549
538− {issue && (
539− <Link
540− to={`${base}/issues/${issue.number}`}
541− className="mt-4 flex items-center gap-2.5 rounded-xl border border-line bg-surface px-4 py-2.5 text-sm transition-colors hover:border-line-strong"
542− >
543− <IssueIcon issue={issue} />
544− <span className="text-muted">
545− {pull.status === "merged" && issue.resolvedBy === pull.number
546− ? "Resolved"
547− : "For issue"}
548− </span>
549− <span className="truncate font-medium">{issue.title}</span>
550− <span className="text-faint">#{issue.number}</span>
551− {issue.pullCount > 1 && (
552− <span className="ml-auto shrink-0 text-xs text-faint">
553− 1 of {issue.pullCount} pull requests
554− </span>
555− )}
556− </Link>
557− )}
558− {pull.status === "merged" && (
559− <p className="mt-4 flex items-center gap-2.5 rounded-xl border border-merged/40 bg-merged/5 px-4 py-2.5 text-sm">
560− <GitMerge size={16} className="shrink-0 text-merged" />
561− <span>
562− Merged into <span className="font-mono">{defaultBranch}</span> by{" "}
563− <span className="font-medium">{pull.mergedBy}</span>{" "}
564− {/* Not an account: the repository's settings said to merge it. */}
565− {pull.mergedBy === "g1t" && "automatically, once it was ready, "}
566− {pull.mergedAt && <TimeAgo at={pull.mergedAt} />}
567− {pull.headCommit && (
568− <span className="font-mono text-muted"> · {pull.headCommit.slice(0, 7)}</span>
569− )}
570− </span>
571− </p>
572− )}
573− {pull.supersededBy != null && (
574− <p className="mt-4 rounded-xl border border-line bg-surface px-4 py-2.5 text-sm text-muted">
575− Closed because{" "}
576− <Link
577− to={`${base}/pull/${pull.supersededBy}`}
578− className="font-medium text-fg hover:underline"
579− >
580− #{pull.supersededBy}
581− </Link>{" "}
582− was merged for this issue instead.
583− </p>
584− )}
585−
586− <nav className="mt-6 flex gap-6 border-b border-line">
550+ <nav className="mt-6 flex items-end gap-6 border-b border-line">
587551 <TabLink to={here} active={tab === "conversation"}>
588552 <MessageSquare size={15} />
589553 Conversation
554+ <TabCount n={comments.length} />
590555 </TabLink>
591556 <TabLink to={here + "?tab=session"} active={tab === "session"}>
592557 <MessagesSquare size={15} />
594559 </TabLink>
595560 <TabLink to={here + "?tab=changes"} active={tab === "changes"}>
596561 <FileDiff size={15} />
597− Changes
562+ Files changed
563+ <TabCount n={pull.files.length} />
598564 </TabLink>
565+ {pull.files.length > 0 && (
566+ <span className="ml-auto hidden pb-2.5 font-mono text-xs sm:inline">
567+ <span className="text-accent">+{pull.files.reduce((sum, file) => sum + file.additions, 0).toLocaleString("en-US")}</span>{" "}
568+ <span className="text-danger">−{pull.files.reduce((sum, file) => sum + file.deletions, 0).toLocaleString("en-US")}</span>
569+ </span>
570+ )}
599571 </nav>
600− {/* What is happening on it now: its stage, its agent, what people told
601− the agent, and what else touches the same files. On the conversation,
602− so the tabs sit right under the title. */}
603− {tab === "conversation" && (
604− <div>
605− {lifecycle && <LifecyclePanel lifecycle={lifecycle} />}
606− {/* The agent on it: who, doing what this minute, for how long, at what cost. */}
607− <AgentPanel owner={params.owner} repo={params.repo} number={pull.number} stage={lifecycle?.stage ?? null} />
608−
609− {/* Steering: while its agent works, people can tell it things. */}
610− {canManage &&
611− pull.runtime === "hosted" &&
612− (working || ["working", "revising", "catching_up", "answering"].includes(lifecycle?.stage ?? "")) && (
613− <Form method="post" className="mt-4 rounded-2xl bg-surface p-4 ring-1 ring-merged/30">
614− <p className="flex items-center gap-2 text-sm font-medium">
615− <Sparkles size={15} className="text-merged" />
616− Message the agent
617− </p>
618− <p className="mt-1 text-xs text-muted">
619− A correction, a hint, a change of plan. It reads it at its next step, without
620− starting over.
621− </p>
622− <div className="mt-3 flex gap-2">
623− <input type="hidden" name="action" value="message" />
624− <input
625− name="body"
626− required
627− autoComplete="off"
628− data-1p-ignore
629− placeholder="Keep the old flag working too…"
630− className="h-9 min-w-0 grow rounded-md bg-bg px-3 text-sm ring-1 ring-line outline-none placeholder:text-faint focus:ring-merged/60"
631− />
632− <Button type="submit">Send</Button>
633− </div>
634− </Form>
635− )}
636− {messages.length > 0 && (
637− <ul className="mt-3 space-y-1.5">
638− {messages.map((message) => (
639− <li key={message.id} className="flex items-start gap-2 text-sm">
640− <Avatar name={message.author} size={18} />
641− <span className="min-w-0 grow">
642− <span className="font-medium">
643− {message.fromNumber != null ? `The agent on #${message.fromNumber}` : message.author}
644− </span>{" "}
645− <span className="text-muted">
646− {message.kind === "question"
647− ? "asked:"
648− : message.kind === "handoff"
649− ? "handed over:"
650− : message.kind === "answer"
651− ? "answered:"
652− : "to the agent:"}
653− </span>{" "}
654− {message.body}
655− {message.answer && (
656− <span className="mt-1 block border-l-2 border-merged/40 pl-2 text-muted">
657− {message.declined ? "Declined: " : "Answer: "}
658− {message.answer}
659− </span>
660− )}
661− </span>
662− <span className={`shrink-0 text-xs ${message.deliveredAt ? "text-accent" : "text-faint"}`}>
663− {message.deliveredAt ? "read by the agent" : "waiting for its next step"}
572+ </header>
573+ <div className={`mt-6 grid gap-8 ${tab === "changes" ? "" : "lg:grid-cols-[1fr_19rem]"}`}>
574+ <div className="min-w-0">
575+ {/* What is happening on it now: its stage, its agent, what people told
576+ the agent, and what else touches the same files. On the conversation,
577+ so the tabs sit right under the title. */}
578+ {tab === "conversation" && (
579+ <div>
580+ {issue && (
581+ <Link
582+ to={`${base}/issues/${issue.number}`}
583+ className="mt-4 flex items-center gap-2.5 rounded-xl border border-line bg-surface px-4 py-2.5 text-sm transition-colors hover:border-line-strong"
584+ >
585+ <IssueIcon issue={issue} />
586+ <span className="text-muted">
587+ {pull.status === "merged" && issue.resolvedBy === pull.number
588+ ? "Resolved"
589+ : "For issue"}
590+ </span>
591+ <span className="truncate font-medium">{issue.title}</span>
592+ <span className="text-faint">#{issue.number}</span>
593+ {issue.pullCount > 1 && (
594+ <span className="ml-auto shrink-0 text-xs text-faint">
595+ 1 of {issue.pullCount} pull requests
664596 </span>
665− </li>
666− ))}
667− </ul>
668− )}
669− {collisions.length > 0 && active && (
670− <div className="mt-4 rounded-xl border border-line bg-surface px-4 py-3 text-sm">
671− <p className="flex items-center gap-2.5 font-medium">
672− <Radar size={16} className="shrink-0 text-info" />
673− Other work is changing the same files
674− </p>
675− <ul className="mt-2 space-y-1.5">
676− {collisions.map((other) => (
677− <li key={other.number} className="flex flex-wrap items-baseline gap-x-2 text-muted">
678− <Link
679− to={`${base}/pull/${other.number}`}
680− className="font-medium text-fg hover:underline"
681− >
682− {other.title} <span className="font-normal text-faint">#{other.number}</span>
683− </Link>
684− <span className="font-mono text-xs">{other.paths.join(", ")}</span>
685− </li>
686− ))}
687− </ul>
688− <p className="mt-2 text-xs text-faint">
689− Whichever merges second will have to catch up, and may conflict.
690− </p>
691− </div>
692− )}
693− </div>
694− )}
695− <div className="mt-5">
696− {comparison ? (
697− <DiffView
698− comparison={comparison}
699− review={{
700− comments: comments.filter((comment) => comment.path),
701− canComment: Boolean(viewer),
702− }}
703− />
704− ) : tab === "session" ? (
705− session.length === 0 ? (
706− <EmptyState title="Nothing recorded yet">
707− The agent's prompts, reasoning and tool calls appear here as it
708− works.
709− </EmptyState>
710− ) : (
711− <ol className="relative space-y-5 before:absolute before:top-2 before:bottom-2 before:left-3.25 before:w-px before:bg-line">
712− {session.map((entry) => (
713− <Entry key={entry.seq} entry={entry} agent={pull.agent} />
714− ))}
715− </ol>
716− )
717− ) : (
718− <div className="space-y-4">
719− <TimelineItem
720− author={pull.author.username}
721− at={pull.createdAt}
722− action={
597+ )}
598+ </Link>
599+ )}
600+ {pull.status === "merged" && (
601+ <p className="mt-4 flex items-center gap-2.5 rounded-xl border border-merged/40 bg-merged/5 px-4 py-2.5 text-sm">
602+ <GitMerge size={16} className="shrink-0 text-merged" />
723603 <span>
724− opened this pull request
725− {!pull.branch && (
726− <>
727− {" "}
728− with <span className="font-mono text-xs">{pull.agent}</span>
729− </>
604+ Merged into <span className="font-mono">{defaultBranch}</span> by{" "}
605+ <span className="font-medium">{pull.mergedBy}</span>{" "}
606+ {/* Not an account: the repository's settings said to merge it. */}
607+ {pull.mergedBy === "g1t" && "automatically, once it was ready, "}
608+ {pull.mergedAt && <TimeAgo at={pull.mergedAt} />}
609+ {pull.headCommit && (
610+ <span className="font-mono text-muted"> · {pull.headCommit.slice(0, 7)}</span>
730611 )}
731612 </span>
732− }
733− >
734− {pull.body ? (
735− <Markdown source={pull.body} repo={{ namespace: params.owner, name: params.repo }} />
736− ) : (
737− <p className="text-sm text-muted">
738− {working
739− ? "No description yet. It is written when the pull request is marked ready for review."
740− : "No description."}
741− </p>
742− )}
743− {canManage && pull.status === "open" && (
744− <details className="mt-3 border-t border-line pt-3 text-sm">
745− <summary className="cursor-pointer text-xs text-faint hover:text-fg">
746− Edit description
747− </summary>
748− <Form method="post" className="mt-3 space-y-2">
749− <Textarea
750− name="summary"
751− rows={6}
752− placeholder="What changed and why"
753− defaultValue={pull.body ?? ""}
613+ </p>
614+ )}
615+ {pull.supersededBy != null && (
616+ <p className="mt-4 rounded-xl border border-line bg-surface px-4 py-2.5 text-sm text-muted">
617+ Closed because{" "}
618+ <Link
619+ to={`${base}/pull/${pull.supersededBy}`}
620+ className="font-medium text-fg hover:underline"
621+ >
622+ #{pull.supersededBy}
623+ </Link>{" "}
624+ was merged for this issue instead.
625+ </p>
626+ )}
627+ {lifecycle && <LifecyclePanel lifecycle={lifecycle} />}
628+ {/* The agent on it: who, doing what this minute, for how long, at what cost. */}
629+ <AgentPanel owner={params.owner} repo={params.repo} number={pull.number} stage={lifecycle?.stage ?? null} />
630+
631+ {/* Steering: while its agent works, people can tell it things. */}
632+ {canManage &&
633+ pull.runtime === "hosted" &&
634+ (working || ["working", "revising", "catching_up", "answering"].includes(lifecycle?.stage ?? "")) && (
635+ <Form method="post" className="mt-4 rounded-2xl bg-surface p-4 ring-1 ring-merged/30">
636+ <p className="flex items-center gap-2 text-sm font-medium">
637+ <Sparkles size={15} className="text-merged" />
638+ Message the agent
639+ </p>
640+ <p className="mt-1 text-xs text-muted">
641+ A correction, a hint, a change of plan. It reads it at its next step, without
642+ starting over.
643+ </p>
644+ <div className="mt-3 flex gap-2">
645+ <input type="hidden" name="action" value="message" />
646+ <input
647+ name="body"
648+ required
649+ autoComplete="off"
650+ data-1p-ignore
651+ placeholder="Keep the old flag working too…"
652+ className="h-9 min-w-0 grow rounded-md bg-bg px-3 text-sm ring-1 ring-line outline-none placeholder:text-faint focus:ring-merged/60"
754653 />
755− <Button type="submit">Save</Button>
756− </Form>
757− </details>
654+ <Button type="submit">Send</Button>
655+ </div>
656+ </Form>
758657 )}
759− </TimelineItem>
658+ {messages.length > 0 && (
659+ <ul className="mt-3 space-y-1.5">
660+ {messages.map((message) => (
661+ <li key={message.id} className="flex items-start gap-2 text-sm">
662+ <Avatar name={message.author} size={18} />
663+ <span className="min-w-0 grow">
664+ <span className="font-medium">
665+ {message.fromNumber != null ? `The agent on #${message.fromNumber}` : message.author}
666+ </span>{" "}
667+ <span className="text-muted">
668+ {message.kind === "question"
669+ ? "asked:"
670+ : message.kind === "handoff"
671+ ? "handed over:"
672+ : message.kind === "answer"
673+ ? "answered:"
674+ : "to the agent:"}
675+ </span>{" "}
676+ {message.body}
677+ {message.answer && (
678+ <span className="mt-1 block border-l-2 border-merged/40 pl-2 text-muted">
679+ {message.declined ? "Declined: " : "Answer: "}
680+ {message.answer}
681+ </span>
682+ )}
683+ </span>
684+ <span className={`shrink-0 text-xs ${message.deliveredAt ? "text-accent" : "text-faint"}`}>
685+ {message.deliveredAt ? "read by the agent" : "waiting for its next step"}
686+ </span>
687+ </li>
688+ ))}
689+ </ul>
690+ )}
691+ {collisions.length > 0 && active && (
692+ <div className="mt-4 rounded-xl border border-line bg-surface px-4 py-3 text-sm">
693+ <p className="flex items-center gap-2.5 font-medium">
694+ <Radar size={16} className="shrink-0 text-info" />
695+ Other work is changing the same files
696+ </p>
697+ <ul className="mt-2 space-y-1.5">
698+ {collisions.map((other) => (
699+ <li key={other.number} className="flex flex-wrap items-baseline gap-x-2 text-muted">
700+ <Link
701+ to={`${base}/pull/${other.number}`}
702+ className="font-medium text-fg hover:underline"
703+ >
704+ {other.title} <span className="font-normal text-faint">#{other.number}</span>
705+ </Link>
706+ <span className="font-mono text-xs">{other.paths.join(", ")}</span>
707+ </li>
708+ ))}
709+ </ul>
710+ <p className="mt-2 text-xs text-faint">
711+ Whichever merges second will have to catch up, and may conflict.
712+ </p>
713+ </div>
714+ )}
715+ </div>
716+ )}
717+ <div className="mt-5">
718+ {comparison ? (
719+ <DiffView
720+ comparison={comparison}
721+ review={{
722+ comments: comments.filter((comment) => comment.path),
723+ canComment: Boolean(viewer),
724+ }}
725+ />
726+ ) : tab === "session" ? (
727+ session.length === 0 ? (
728+ <EmptyState title="Nothing recorded yet">
729+ The agent's prompts, reasoning and tool calls appear here as it
730+ works.
731+ </EmptyState>
732+ ) : (
733+ <ol className="relative space-y-5 before:absolute before:top-2 before:bottom-2 before:left-3.25 before:w-px before:bg-line">
734+ {session.map((entry) => (
735+ <Entry key={entry.seq} entry={entry} agent={pull.agent} />
736+ ))}
737+ </ol>
738+ )
739+ ) : (
740+ <div className="space-y-4">
741+ <TimelineItem
742+ author={pull.author.username}
743+ at={pull.createdAt}
744+ action={
745+ <span>
746+ opened this pull request
747+ {!pull.branch && (
748+ <>
749+ {" "}
750+ with <span className="font-mono text-xs">{pull.agent}</span>
751+ </>
752+ )}
753+ </span>
754+ }
755+ >
756+ {pull.body ? (
757+ <Markdown source={pull.body} repo={{ namespace: params.owner, name: params.repo }} />
758+ ) : (
759+ <p className="text-sm text-muted">
760+ {working
761+ ? "No description yet. It is written when the pull request is marked ready for review."
762+ : "No description."}
763+ </p>
764+ )}
765+ {canManage && pull.status === "open" && (
766+ <details className="mt-3 border-t border-line pt-3 text-sm">
767+ <summary className="cursor-pointer text-xs text-faint hover:text-fg">
768+ Edit description
769+ </summary>
770+ <Form method="post" className="mt-3 space-y-2">
771+ <Textarea
772+ name="summary"
773+ rows={6}
774+ placeholder="What changed and why"
775+ defaultValue={pull.body ?? ""}
776+ />
777+ <Button type="submit">Save</Button>
778+ </Form>
779+ </details>
780+ )}
781+ </TimelineItem>
760782
761− <CommentList comments={comments} review={review} base={base} />
783+ <CommentList comments={comments} review={review} base={base} />
762784
763− {(preview || build) && (
764− <DeploymentCard preview={preview} build={build} stacked={stacked} base={base} />
765− )}
785+ {(preview || build) && (
786+ <DeploymentCard preview={preview} build={build} stacked={stacked} base={base} />
787+ )}
766788
767− {pull.status === "draft" && (
768− <StatusBox>
769− <StatusRow icon={<Loader size={16} className="text-faint" />} title="This is a draft">
770− It is still being worked on. It can be reviewed and merged once it is
771− marked ready.
772− </StatusRow>
773− {canManage && (
774− <Form method="post" className="space-y-2 px-4 py-3">
775− <Textarea name="summary" rows={3} placeholder="What changed and why" />
776− <Button type="submit">Mark ready for review</Button>
777− </Form>
778− )}
779− </StatusBox>
780− )}
789+ {pull.status === "draft" && (
790+ <StatusBox>
791+ <StatusRow icon={<Loader size={16} className="text-faint" />} title="This is a draft">
792+ It is still being worked on. It can be reviewed and merged once it is
793+ marked ready.
794+ </StatusRow>
795+ {canManage && (
796+ <Form method="post" className="space-y-2 px-4 py-3">
797+ <Textarea name="summary" rows={3} placeholder="What changed and why" />
798+ <Button type="submit">Mark ready for review</Button>
799+ </Form>
800+ )}
801+ </StatusBox>
802+ )}
781803
782− {pull.status === "open" && (
783− <StatusBox>
784− <ChecksSection
785− run={checks}
786− commands={issue?.checks ?? []}
787− statuses={statuses}
788− jobs={workflowJobs}
789− pull={pull}
790− base={base}
791− earlier={earlierChecks}
792− canRerun={canManage}
793− canRerunWorkflows={canMerge}
794− error={
795− actionData?.action === "recheck" || actionData?.action === "rerun-workflow"
796− ? actionData.error
797− : null
798− }
799− />
800− <StatusRow
801− icon={
802− reviews.some(({ verdict }) => verdict === "request_changes") ? (
803− <CircleSlash size={16} className="text-danger" />
804− ) : reviews.length > 0 ? (
805− <CircleCheck size={16} className="text-accent" />
806− ) : (
807− <MessageSquare size={16} className="text-faint" />
808− )
809− }
810− title={
811− reviews.length === 0
812− ? "No reviews yet"
813− : reviews
814− .map(
815− ({ reviewer, verdict }) =>
816− `${verdict === "approve" ? "Approved by" : "Changes requested by"} ${reviewer}`,
817− )
818− .join(" · ")
819− }
820− >
821− {reviewPending && "A g1t agent is reviewing it now. "}
822− {requiredApprovals > 0 &&
823− `This repository requires ${requiredApprovals} approving ${
824− requiredApprovals === 1 ? "review" : "reviews"
825− } before merging.`}
826− </StatusRow>
827− {landing ? (
828− <StatusRow
829− icon={<Loader size={16} className="animate-spin text-accent" />}
830− title="Merging"
831− >
832− {defaultBranch} has moved, so g1t is bringing this up to date first. It
833− lands as soon as that is done. Watch it in the Session tab.
834− </StatusRow>
835− ) : conflicting ? (
836− <ConflictsSection
837− conflicts={conflicts}
804+ {pull.status === "open" && (
805+ <StatusBox>
806+ <ChecksSection
807+ run={checks}
808+ commands={issue?.checks ?? []}
809+ statuses={statuses}
810+ jobs={workflowJobs}
838811 pull={pull}
839− owner={params.owner}
840− repo={params.repo}
841− defaultBranch={defaultBranch}
842− changesUrl={here + "?tab=changes"}
843− canResolve={canUpdate && agentsEnabled}
844− resolving={resolving}
845− onResolve={() => setSubmitted("update")}
846− error={actionData?.action === "update" ? actionData.error : null}
812+ base={base}
813+ earlier={earlierChecks}
814+ canRerun={canManage}
815+ canRerunWorkflows={canMerge}
816+ error={
817+ actionData?.action === "recheck" || actionData?.action === "rerun-workflow"
818+ ? actionData.error
819+ : null
820+ }
847821 />
848− ) : probing ? (
849− <MergeabilityRow mergeable={mergeable} defaultBranch={defaultBranch} />
850− ) : behind ? (
851822 <StatusRow
852− icon={<GitPullRequestArrow size={16} className="text-info" />}
853− title={`${defaultBranch} has moved since this was made`}
823+ icon={
824+ reviews.some(({ verdict }) => verdict === "request_changes") ? (
825+ <CircleSlash size={16} className="text-danger" />
826+ ) : reviews.length > 0 ? (
827+ <CircleCheck size={16} className="text-accent" />
828+ ) : (
829+ <MessageSquare size={16} className="text-faint" />
830+ )
831+ }
832+ title={
833+ reviews.length === 0
834+ ? "No reviews yet"
835+ : reviews
836+ .map(
837+ ({ reviewer, verdict }) =>
838+ `${verdict === "approve" ? "Approved by" : "Changes requested by"} ${reviewer}`,
839+ )
840+ .join(" · ")
841+ }
854842 >
855− {mergeable === "clean" && "It has no conflicts with it. "}
856− {requireUpToDate
857− ? "This repository requires pull requests to be up to date, so it has to catch up before it can merge."
858− : "That does not stop it merging: it is brought up to date as part of the merge."}
859− {canUpdate && agentsEnabled && (
860− <Form
861− method="post"
862− className="mt-2"
863− onSubmit={() => setSubmitted("update")}
864− >
865− <input type="hidden" name="action" value="update" />
866− <Button variant="quiet" type="submit" disabled={catchingUp}>
867− {catchingUp ? "Catching up…" : `Catch up with ${defaultBranch} now`}
868− </Button>
869− </Form>
870− )}
871− {actionData?.action === "update" && (
872− <ErrorText>{actionData.error}</ErrorText>
873− )}
843+ {reviewPending && "A g1t agent is reviewing it now. "}
844+ {requiredApprovals > 0 &&
845+ `This repository requires ${requiredApprovals} approving ${
846+ requiredApprovals === 1 ? "review" : "reviews"
847+ } before merging.`}
874848 </StatusRow>
875− ) : (
876− pull.headCommit && (
849+ {landing ? (
877850 <StatusRow
878− icon={<CircleCheck size={16} className="text-accent" />}
879− title={`Up to date with ${defaultBranch}`}
851+ icon={<Loader size={16} className="animate-spin text-accent" />}
852+ title="Merging"
853+ >
854+ {defaultBranch} has moved, so g1t is bringing this up to date first. It
855+ lands as soon as that is done. Watch it in the Session tab.
856+ </StatusRow>
857+ ) : conflicting ? (
858+ <ConflictsSection
859+ conflicts={conflicts}
860+ pull={pull}
861+ owner={params.owner}
862+ repo={params.repo}
863+ defaultBranch={defaultBranch}
864+ changesUrl={here + "?tab=changes"}
865+ canResolve={canUpdate && agentsEnabled}
866+ resolving={resolving}
867+ onResolve={() => setSubmitted("update")}
868+ error={actionData?.action === "update" ? actionData.error : null}
880869 />
881− )
882− )}
883− {stalled && !lifecycle && (
884− <StatusRow icon={<Hand size={16} className="text-warn" />} title="Needs you">
885− {stalled}
886− </StatusRow>
887− )}
888− {canMerge && lifecycle?.stage === "queued" && (
889− <Form method="post" className="flex flex-wrap items-center gap-3 px-4 py-3">
890− <ButtonLink to={`${base}/queue`}>
891− <Layers size={15} />
892− See the queue
893− </ButtonLink>
894− <Button variant="quiet" type="submit" name="action" value="unqueue">
895− Remove from the queue
896− </Button>
897− </Form>
898− )}
899− {canMerge && !landing && lifecycle?.stage !== "queued" && (
900− <Form method="post" className="space-y-3 px-4 py-3">
901− {issue?.state === "open" && (
902− <CheckboxOption
903− name="keepIssueOpen"
904− label={`Keep #${issue.number} open. This is only part of the work.`}
905− labelClassName="text-xs text-muted"
906− />
907− )}
908− {unchecked && canIgnoreChecks && (
909− <CheckboxOption
910− name="ignoreChecks"
911− label={`Merge although the checks ${checking ? "have not finished" : "did not pass"}.`}
912− labelClassName="text-xs text-muted"
870+ ) : probing ? (
871+ <MergeabilityRow mergeable={mergeable} defaultBranch={defaultBranch} />
872+ ) : behind ? (
873+ <StatusRow
874+ icon={<GitPullRequestArrow size={16} className="text-info" />}
875+ title={`${defaultBranch} has moved since this was made`}
876+ >
877+ {mergeable === "clean" && "It has no conflicts with it. "}
878+ {requireUpToDate
879+ ? "This repository requires pull requests to be up to date, so it has to catch up before it can merge."
880+ : "That does not stop it merging: it is brought up to date as part of the merge."}
881+ {canUpdate && agentsEnabled && (
882+ <Form
883+ method="post"
884+ className="mt-2"
885+ onSubmit={() => setSubmitted("update")}
886+ >
887+ <input type="hidden" name="action" value="update" />
888+ <Button variant="quiet" type="submit" disabled={catchingUp}>
889+ {catchingUp ? "Catching up…" : `Catch up with ${defaultBranch} now`}
890+ </Button>
891+ </Form>
892+ )}
893+ {actionData?.action === "update" && (
894+ <ErrorText>{actionData.error}</ErrorText>
895+ )}
896+ </StatusRow>
897+ ) : (
898+ pull.headCommit && (
899+ <StatusRow
900+ icon={<CircleCheck size={16} className="text-accent" />}
901+ title={`Up to date with ${defaultBranch}`}
913902 />
914− )}
915− <div className="flex flex-wrap items-center gap-3">
916− <Button
917− variant="accent"
918− type="submit"
919− name="action"
920− value="merge"
921− disabled={mergeBlocked != null}
922− title={mergeBlocked ?? undefined}
923− >
924− {mergeQueue ? <Layers size={15} /> : <GitMerge size={15} />}
925− {mergeQueue ? "Add to the merge queue" : `Merge into ${defaultBranch}`}
903+ )
904+ )}
905+ {stalled && !lifecycle && (
906+ <StatusRow icon={<Hand size={16} className="text-warn" />} title="Needs you">
907+ {stalled}
908+ </StatusRow>
909+ )}
910+ {canMerge && lifecycle?.stage === "queued" && (
911+ <Form method="post" className="flex flex-wrap items-center gap-3 px-4 py-3">
912+ <ButtonLink to={`${base}/queue`}>
913+ <Layers size={15} />
914+ See the queue
915+ </ButtonLink>
916+ <Button variant="quiet" type="submit" name="action" value="unqueue">
917+ Remove from the queue
926918 </Button>
927− <span className={`text-xs ${mergeBlocked ? "text-danger" : "text-muted"}`}>
928− {mergeBlocked ?? (mergeQueue
929− ? `Tested together with everything ahead of it, then lands on ${defaultBranch}.`
930− : issue?.state === "open"
931− ? `Closes issue #${issue.number}, and any other pull requests still open for it.`
932− : `Lands its commits on ${defaultBranch}.`)}
933− </span>
934− </div>
935− {actionData?.action === "merge" && (
936− <ErrorText>{actionData.error}</ErrorText>
937− )}
938− </Form>
939− )}
940− </StatusBox>
941− )}
919+ </Form>
920+ )}
921+ {canMerge && !landing && lifecycle?.stage !== "queued" && (
922+ <Form method="post" className="space-y-3 px-4 py-3">
923+ {issue?.state === "open" && (
924+ <CheckboxOption
925+ name="keepIssueOpen"
926+ label={`Keep #${issue.number} open. This is only part of the work.`}
927+ labelClassName="text-xs text-muted"
928+ />
929+ )}
930+ {unchecked && canIgnoreChecks && (
931+ <CheckboxOption
932+ name="ignoreChecks"
933+ label={`Merge although the checks ${checking ? "have not finished" : "did not pass"}.`}
934+ labelClassName="text-xs text-muted"
935+ />
936+ )}
937+ <div className="flex flex-wrap items-center gap-3">
938+ <Button
939+ variant="accent"
940+ type="submit"
941+ name="action"
942+ value="merge"
943+ disabled={mergeBlocked != null}
944+ title={mergeBlocked ?? undefined}
945+ >
946+ {mergeQueue ? <Layers size={15} /> : <GitMerge size={15} />}
947+ {mergeQueue ? "Add to the merge queue" : `Merge into ${defaultBranch}`}
948+ </Button>
949+ <span className={`text-xs ${mergeBlocked ? "text-danger" : "text-muted"}`}>
950+ {mergeBlocked ?? (mergeQueue
951+ ? `Tested together with everything ahead of it, then lands on ${defaultBranch}.`
952+ : issue?.state === "open"
953+ ? `Closes issue #${issue.number}, and any other pull requests still open for it.`
954+ : `Lands its commits on ${defaultBranch}.`)}
955+ </span>
956+ </div>
957+ {actionData?.action === "merge" && (
958+ <ErrorText>{actionData.error}</ErrorText>
959+ )}
960+ </Form>
961+ )}
962+ </StatusBox>
963+ )}
942964
943− <CommentForm
944− author={viewer?.username ?? null}
945− resetKey={comments.length}
946− review={review}
947− />
948− {canManage && active && (
949− <Form method="post" className="flex justify-end">
950− <Button variant="quiet" type="submit" name="action" value="close">
951− Close pull request
952− </Button>
965+ <CommentForm
966+ author={viewer?.username ?? null}
967+ resetKey={comments.length}
968+ review={review}
969+ />
970+ {canManage && active && (
971+ <Form method="post" className="flex justify-end">
972+ <Button variant="quiet" type="submit" name="action" value="close">
973+ Close pull request
974+ </Button>
975+ </Form>
976+ )}
977+ {actionData &&
978+ !["merge", "comment", "recheck", "rerun-workflow", "update", "agent-review", "reviewers", "assign"].includes(
979+ String(actionData.action),
980+ ) && <ErrorText>{actionData.error}</ErrorText>}
981+ </div>
982+ )}
983+ {actionData?.action === "comment" && (
984+ <div className="mt-2">
985+ <ErrorText>{actionData.error}</ErrorText>
986+ </div>
987+ )}
988+ </div>
989+ </div>
990+
991+ <aside className={tab === "changes" ? "hidden" : "space-y-6"}>
992+ {/* An open pull request shows its checks in full in the merge box. */}
993+ {pull.status !== "open" && (
994+ <>
995+ <ChecksPanel run={checks} commands={issue?.checks ?? []} canRerun={false} />
996+ <WorkflowStatuses statuses={statuses} />
997+ </>
998+ )}
999+ {affects.length > 0 && (
1000+ <section>
1001+ <h3 className="flex items-center gap-1.5 text-sm font-medium">
1002+ <Network size={14} className="text-faint" />
1003+ Affects
1004+ </h3>
1005+ <p className="mt-1 text-xs text-muted">Projects that use this one, and so may feel this change:</p>
1006+ <ul className="mt-2 space-y-1 text-sm">
1007+ {affects.map((project) => (
1008+ <li key={project.slug} className="flex items-center justify-between gap-2">
1009+ <Link to={`/${params.owner}/${project.slug}`} className="hover:underline">
1010+ {project.name}
1011+ </Link>
1012+ {project.as && <code className="font-mono text-xs text-faint">{project.as}</code>}
1013+ </li>
1014+ ))}
1015+ </ul>
1016+ {preview?.branch && canMerge && (
1017+ <Form method="post" className="mt-3">
1018+ <input type="hidden" name="action" value="stack" />
1019+ <input type="hidden" name="branch" value={preview.branch} />
1020+ <button
1021+ type="submit"
1022+ className="w-full rounded-md border border-line px-3 py-1.5 text-xs text-muted transition-colors hover:border-line-strong hover:text-fg"
1023+ >
1024+ Preview them against this change
1025+ </button>
9531026 </Form>
9541027 )}
955− {actionData &&
956− !["merge", "comment", "recheck", "rerun-workflow", "update", "agent-review", "reviewers", "assign"].includes(
957− String(actionData.action),
958− ) && <ErrorText>{actionData.error}</ErrorText>}
959− </div>
960− )}
961− {actionData?.action === "comment" && (
962− <div className="mt-2">
963− <ErrorText>{actionData.error}</ErrorText>
964− </div>
1028+ {actionData?.action === "stack" &&
1029+ ("notice" in actionData ? (
1030+ <p className="mt-2 text-xs text-accent">{String(actionData.notice)}</p>
1031+ ) : (
1032+ <ErrorText>{actionData.error}</ErrorText>
1033+ ))}
1034+ </section>
9651035 )}
966− </div>
967− </div>
9681036
969− <aside className={tab === "changes" ? "hidden" : "space-y-6"}>
970− {/* An open pull request shows its checks in full in the merge box. */}
971− {pull.status !== "open" && (
972− <>
973− <ChecksPanel run={checks} commands={issue?.checks ?? []} canRerun={false} />
974− <WorkflowStatuses statuses={statuses} />
975− </>
976− )}
977− {affects.length > 0 && (
9781037 <section>
979− <h3 className="flex items-center gap-1.5 text-sm font-medium">
980− <Network size={14} className="text-faint" />
981− Affects
982− </h3>
983− <p className="mt-1 text-xs text-muted">Projects that use this one, and so may feel this change:</p>
984− <ul className="mt-2 space-y-1 text-sm">
985− {affects.map((project) => (
986− <li key={project.slug} className="flex items-center justify-between gap-2">
987− <Link to={`/${params.owner}/${project.slug}`} className="hover:underline">
988− {project.name}
989− </Link>
990− {project.as && <code className="font-mono text-xs text-faint">{project.as}</code>}
991− </li>
992− ))}
1038+ <h3 className="text-sm font-medium">Reviewers</h3>
1039+ <ul className="mt-2 space-y-1.5 text-sm">
1040+ {reviewerNames.map((name) => {
1041+ const verdict = reviews.find(({ reviewer }) => reviewer === name)?.verdict;
1042+ const pending = name === "g1t-agent" && reviewPending;
1043+ return (
1044+ <li key={name} className="flex items-center gap-2 px-1">
1045+ {name === "g1t-agent" ? (
1046+ <Sparkles size={16} className="shrink-0 text-accent" />
1047+ ) : (
1048+ <Avatar name={name} size={20} />
1049+ )}
1050+ <span className="grow truncate font-mono text-xs">{name}</span>
1051+ {pending ? (
1052+ <span className="flex items-center gap-1.5 text-xs text-muted">
1053+ <span className="size-1.5 animate-pulse rounded-full bg-accent" />
1054+ Reviewing
1055+ </span>
1056+ ) : verdict === "approve" ? (
1057+ <span className="flex items-center gap-1 text-xs text-accent">
1058+ <CircleCheck size={13} /> Approved
1059+ </span>
1060+ ) : verdict === "request_changes" ? (
1061+ <span className="flex items-center gap-1 text-xs text-danger">
1062+ <CircleSlash size={13} /> Changes requested
1063+ </span>
1064+ ) : (
1065+ <span className="text-xs text-faint">Review requested</span>
1066+ )}
1067+ </li>
1068+ );
1069+ })}
1070+ {reviewerNames.length === 0 && (
1071+ <li className="px-1 text-xs text-faint">No reviews requested yet.</li>
1072+ )}
9931073 </ul>
994− {preview?.branch && canMerge && (
995− <Form method="post" className="mt-3">
996− <input type="hidden" name="action" value="stack" />
997− <input type="hidden" name="branch" value={preview.branch} />
998− <button
999− type="submit"
1000− className="w-full rounded-md border border-line px-3 py-1.5 text-xs text-muted transition-colors hover:border-line-strong hover:text-fg"
1001− >
1002− Preview them against this change
1003− </button>
1004− </Form>
1074+ {pull.status === "open" && canManage && (
1075+ <div className="mt-3 space-y-2">
1076+ {agentsEnabled && !reviewPending && (
1077+ <Form method="post">
1078+ <input type="hidden" name="action" value="agent-review" />
1079+ {pull.reviewers.map((name) => (
1080+ <input key={name} type="hidden" name="reviewer" value={name} />
1081+ ))}
1082+ <div className="*:w-full">
1083+ <Button variant="quiet" type="submit">
1084+ <Sparkles size={14} className="text-accent" />
1085+ Request review from g1t agent
1086+ </Button>
1087+ </div>
1088+ </Form>
1089+ )}
1090+ <details>
1091+ <summary className="cursor-pointer text-xs text-faint hover:text-fg">
1092+ Request review from people
1093+ </summary>
1094+ <Form method="post" className="mt-2 space-y-2" key={pull.reviewers.join()}>
1095+ <input type="hidden" name="action" value="reviewers" />
1096+ {pull.reviewers.includes("g1t-agent") && (
1097+ <input type="hidden" name="reviewer" value="g1t-agent" />
1098+ )}
1099+ <PeoplePicker
1100+ name="reviewer"
1101+ members={members.filter((name) => name !== pull.author.username)}
1102+ chosen={pull.reviewers.filter((name) => name !== "g1t-agent")}
1103+ />
1104+ <Button variant="quiet" type="submit">
1105+ Save reviewers
1106+ </Button>
1107+ </Form>
1108+ </details>
1109+ </div>
10051110 )}
1006− {actionData?.action === "stack" &&
1007− ("notice" in actionData ? (
1008− <p className="mt-2 text-xs text-accent">{String(actionData.notice)}</p>
1009− ) : (
1010− <ErrorText>{actionData.error}</ErrorText>
1011− ))}
1111+ {["agent-review", "reviewers"].includes(String(actionData?.action)) && (
1112+ <ErrorText>{actionData?.error}</ErrorText>
1113+ )}
10121114 </section>
1013− )}
10141115
1015− <section>
1016− <h3 className="text-sm font-medium">Reviewers</h3>
1017− <ul className="mt-2 space-y-1.5 text-sm">
1018− {reviewerNames.map((name) => {
1019− const verdict = reviews.find(({ reviewer }) => reviewer === name)?.verdict;
1020− const pending = name === "g1t-agent" && reviewPending;
1021− return (
1116+ <section>
1117+ <h3 className="text-sm font-medium">Assignees</h3>
1118+ <ul className="mt-2 space-y-1.5 text-sm">
1119+ {pull.assignees.map((name) => (
10221120 <li key={name} className="flex items-center gap-2 px-1">
1023− {name === "g1t-agent" ? (
1024− <Sparkles size={16} className="shrink-0 text-accent" />
1025− ) : (
1026− <Avatar name={name} size={20} />
1027− )}
1121+ <Avatar name={name} size={20} />
10281122 <span className="grow truncate font-mono text-xs">{name}</span>
1029− {pending ? (
1030− <span className="flex items-center gap-1.5 text-xs text-muted">
1031− <span className="size-1.5 animate-pulse rounded-full bg-accent" />
1032− Reviewing
1033− </span>
1034− ) : verdict === "approve" ? (
1035− <span className="flex items-center gap-1 text-xs text-accent">
1036− <CircleCheck size={13} /> Approved
1037− </span>
1038− ) : verdict === "request_changes" ? (
1039− <span className="flex items-center gap-1 text-xs text-danger">
1040− <CircleSlash size={13} /> Changes requested
1041− </span>
1042− ) : (
1043− <span className="text-xs text-faint">Review requested</span>
1044− )}
10451123 </li>
1046− );
1047− })}
1048− {reviewerNames.length === 0 && (
1049− <li className="px-1 text-xs text-faint">No reviews requested yet.</li>
1050− )}
1051− </ul>
1052− {pull.status === "open" && canManage && (
1053− <div className="mt-3 space-y-2">
1054− {agentsEnabled && !reviewPending && (
1055− <Form method="post">
1056− <input type="hidden" name="action" value="agent-review" />
1057− {pull.reviewers.map((name) => (
1058− <input key={name} type="hidden" name="reviewer" value={name} />
1059− ))}
1060− <div className="*:w-full">
1124+ ))}
1125+ {pull.assignees.length === 0 && (
1126+ <li className="px-1 text-xs text-faint">No one yet.</li>
1127+ )}
1128+ </ul>
1129+ {viewer && canManage && active && (
1130+ <div className="mt-3 space-y-2">
1131+ {!pull.assignees.includes(viewer.username) && (
1132+ <Form method="post">
1133+ <input type="hidden" name="action" value="assign" />
1134+ {pull.assignees.map((name) => (
1135+ <input key={name} type="hidden" name="assignee" value={name} />
1136+ ))}
1137+ <input type="hidden" name="assignee" value={viewer.username} />
1138+ <div className="*:w-full">
1139+ <Button variant="quiet" type="submit">
1140+ Assign yourself
1141+ </Button>
1142+ </div>
1143+ </Form>
1144+ )}
1145+ <details>
1146+ <summary className="cursor-pointer text-xs text-faint hover:text-fg">
1147+ Assign people
1148+ </summary>
1149+ <Form method="post" className="mt-2 space-y-2" key={pull.assignees.join()}>
1150+ <input type="hidden" name="action" value="assign" />
1151+ <PeoplePicker name="assignee" members={members} chosen={pull.assignees} />
10611152 <Button variant="quiet" type="submit">
1062− <Sparkles size={14} className="text-accent" />
1063− Request review from g1t agent
1153+ Save assignees
10641154 </Button>
1065− </div>
1066− </Form>
1067− )}
1068− <details>
1069− <summary className="cursor-pointer text-xs text-faint hover:text-fg">
1070− Request review from people
1071− </summary>
1072− <Form method="post" className="mt-2 space-y-2" key={pull.reviewers.join()}>
1073− <input type="hidden" name="action" value="reviewers" />
1074− {pull.reviewers.includes("g1t-agent") && (
1075− <input type="hidden" name="reviewer" value="g1t-agent" />
1076− )}
1077− <PeoplePicker
1078− name="reviewer"
1079− members={members.filter((name) => name !== pull.author.username)}
1080− chosen={pull.reviewers.filter((name) => name !== "g1t-agent")}
1081− />
1082− <Button variant="quiet" type="submit">
1083− Save reviewers
1084− </Button>
1085− </Form>
1086− </details>
1087− </div>
1088− )}
1089− {["agent-review", "reviewers"].includes(String(actionData?.action)) && (
1090− <ErrorText>{actionData?.error}</ErrorText>
1091− )}
1092− </section>
1093−
1094− <section>
1095− <h3 className="text-sm font-medium">Assignees</h3>
1096− <ul className="mt-2 space-y-1.5 text-sm">
1097− {pull.assignees.map((name) => (
1098− <li key={name} className="flex items-center gap-2 px-1">
1099− <Avatar name={name} size={20} />
1100− <span className="grow truncate font-mono text-xs">{name}</span>
1101− </li>
1102− ))}
1103− {pull.assignees.length === 0 && (
1104− <li className="px-1 text-xs text-faint">No one yet.</li>
1155+ </Form>
1156+ </details>
1157+ </div>
11051158 )}
1106− </ul>
1107− {viewer && canManage && active && (
1108− <div className="mt-3 space-y-2">
1109− {!pull.assignees.includes(viewer.username) && (
1110− <Form method="post">
1111− <input type="hidden" name="action" value="assign" />
1112− {pull.assignees.map((name) => (
1113− <input key={name} type="hidden" name="assignee" value={name} />
1114− ))}
1115− <input type="hidden" name="assignee" value={viewer.username} />
1116− <div className="*:w-full">
1117− <Button variant="quiet" type="submit">
1118− Assign yourself
1119− </Button>
1120− </div>
1121− </Form>
1122− )}
1123− <details>
1124− <summary className="cursor-pointer text-xs text-faint hover:text-fg">
1125− Assign people
1126− </summary>
1127− <Form method="post" className="mt-2 space-y-2" key={pull.assignees.join()}>
1128− <input type="hidden" name="action" value="assign" />
1129− <PeoplePicker name="assignee" members={members} chosen={pull.assignees} />
1130− <Button variant="quiet" type="submit">
1131− Save assignees
1132− </Button>
1133− </Form>
1134− </details>
1135− </div>
1136− )}
1137− {actionData?.action === "assign" && <ErrorText>{actionData.error}</ErrorText>}
1138− </section>
1159+ {actionData?.action === "assign" && <ErrorText>{actionData.error}</ErrorText>}
1160+ </section>
11391161
1140− <section>
1141− <h3 className="text-sm font-medium">Working copy</h3>
1142− {pull.branch ? (
1143− <>
1144− <p className="mt-1 text-xs text-muted">
1145− <GitBranch size={13} className="mr-1 inline align-[-2px]" />
1146− Branch <span className="font-mono text-fg">{pull.branch}</span> of this
1147− repository. Pushes to it show up here.
1148− </p>
1149− <div className="mt-2">
1150− <CopyLine text={`git clone -b ${pull.branch} ${remote}`} />
1151− </div>
1152− </>
1153− ) : (
1154− <>
1155− <p className="mt-1 text-xs text-muted">
1156− This fork belongs to the pull request. Pushes to it show up here.
1157− </p>
1158− <div className="mt-2">
1159− <CopyLine text={`git clone ${remote}`} />
1160− </div>
1161− </>
1162− )}
1163− <p className="mt-3 flex items-center gap-1.5 font-mono text-xs text-faint">
1164− <GitCommitHorizontal size={13} />
1165− {pull.headCommit?.slice(0, 12) ?? "no commits pushed yet"}
1166− </p>
1167− </section>
1168− </aside>
1162+ <section>
1163+ <h3 className="text-sm font-medium">Working copy</h3>
1164+ {pull.branch ? (
1165+ <>
1166+ <p className="mt-1 text-xs text-muted">
1167+ <GitBranch size={13} className="mr-1 inline align-[-2px]" />
1168+ Branch <span className="font-mono text-fg">{pull.branch}</span> of this
1169+ repository. Pushes to it show up here.
1170+ </p>
1171+ <div className="mt-2">
1172+ <CopyLine text={`git clone -b ${pull.branch} ${remote}`} />
1173+ </div>
1174+ </>
1175+ ) : (
1176+ <>
1177+ <p className="mt-1 text-xs text-muted">
1178+ This fork belongs to the pull request. Pushes to it show up here.
1179+ </p>
1180+ <div className="mt-2">
1181+ <CopyLine text={`git clone ${remote}`} />
1182+ </div>
1183+ </>
1184+ )}
1185+ <p className="mt-3 flex items-center gap-1.5 font-mono text-xs text-faint">
1186+ <GitCommitHorizontal size={13} />
1187+ {pull.headCommit?.slice(0, 12) ?? "no commits pushed yet"}
1188+ </p>
1189+ </section>
1190+ </aside>
1191+ </div>
11691192 </div>
11701193 );
11711194 }