Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions apps/web/src/components/CommandPalette.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,6 @@ import {
FileSearchIcon,
FolderIcon,
FolderPlusIcon,
GitPullRequestArrowIcon,
LinkIcon,
MessageSquareIcon,
PaletteIcon,
Expand Down Expand Up @@ -181,6 +180,7 @@ import {
buildSidebarProjectSnapshots,
} from "../sidebarProjectGrouping";
import type { Project } from "../types";
import { PullRequestGlyph } from "~/components/pullRequest/pullRequestIcons";

const EMPTY_BROWSE_ENTRIES: FilesystemBrowseResult["entries"] = [];

Expand Down Expand Up @@ -1706,7 +1706,7 @@ function OpenCommandPaletteDialog(props: {
value: "action:link-pull-request",
searchTerms: ["link", "pull request", "pr", "attach", "stack"],
title: "Link pull request to thread",
icon: <GitPullRequestArrowIcon className={ITEM_ICON_CLASS} />,
icon: <PullRequestGlyph.link className={ITEM_ICON_CLASS} />,
run: async () => {
openLinkPullRequestDialog(threadRef);
},
Expand All @@ -1718,7 +1718,7 @@ function OpenCommandPaletteDialog(props: {
searchTerms: ["pull requests", "linked", "stack", "prs"],
title: "Show linked pull requests",
disabled: visibleThreadPullRequests(activeThread.pullRequests).length === 0,
icon: <GitPullRequestArrowIcon className={ITEM_ICON_CLASS} />,
icon: <PullRequestGlyph.link className={ITEM_ICON_CLASS} />,
run: async () => {
useRightPanelStore.getState().open(threadRef, "pull-requests");
},
Expand Down
4 changes: 2 additions & 2 deletions apps/web/src/components/LegacySidebar.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { useSupportsMultiplePullRequests } from "~/hooks/useSupportsMultiplePullRequests";
import { GitPullRequestIcon } from "lucide-react";
import { resolveThreadCurrentPullRequestLink } from "@t3tools/shared/threadPullRequests";
import { Spinner } from "~/components/ui/spinner";
import {
Expand Down Expand Up @@ -214,6 +213,7 @@ import {
type SidebarProjectGroupMember,
type SidebarProjectSnapshot,
} from "../sidebarProjectGrouping";
import { PullRequestGlyph } from "~/components/pullRequest/pullRequestIcons";
const SIDEBAR_SORT_LABELS: Record<SidebarProjectSortOrder, string> = {
updated_at: "Last user message",
created_at: "Created at",
Expand Down Expand Up @@ -757,7 +757,7 @@ const SidebarThreadRow = memo(function SidebarThreadRow(props: SidebarThreadRowP
className="text-muted-foreground"
aria-label={`PR #${currentLinkedPr.number}, status pending`}
>
<GitPullRequestIcon className="size-3" />
<PullRequestGlyph.pullRequest className="size-3" />
</a>
) : null}
{threadStatus && <ThreadStatusLabel status={threadStatus} />}
Expand Down
20 changes: 10 additions & 10 deletions apps/web/src/components/RightPanelTabs.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,6 @@ import {
ChevronRight,
FileDiff,
Files,
GitPullRequest,
GitPullRequestArrow,
Globe2,
Plus,
TerminalSquare,
Expand Down Expand Up @@ -73,6 +71,7 @@ import { FaviconImage } from "./preview/PreviewFaviconIcon";
import { previewBridge } from "./preview/previewBridge";
import { PierreEntryIcon } from "./chat/PierreEntryIcon";
import { resolvePullRequestState } from "./pullRequest/pullRequestPresentation";
import { PullRequestGlyph } from "~/components/pullRequest/pullRequestIcons";

interface RightPanelTabsProps {
mode: PreviewPanelMode;
Expand Down Expand Up @@ -373,7 +372,7 @@ function RightPanelEmptyState(props: {
},
{
label: "Pull request",
icon: GitPullRequest,
icon: PullRequestGlyph.pullRequest,
shortcut: "P",
available: props.pullRequestAvailable,
disabledReason: SURFACE_UNAVAILABLE_HINTS.pullRequest,
Expand All @@ -382,7 +381,7 @@ function RightPanelEmptyState(props: {
},
{
label: "Linked pull requests",
icon: GitPullRequestArrow,
icon: PullRequestGlyph.link,
shortcut: "L",
available: props.pullRequestsAvailable,
disabledReason: SURFACE_UNAVAILABLE_HINTS.pullRequests,
Expand Down Expand Up @@ -712,7 +711,7 @@ function SurfaceIcon({
/>
);
case "pull-requests":
return <GitPullRequestArrow className="size-3 shrink-0" />;
return <PullRequestGlyph.link className="size-3 shrink-0" />;
case "agents":
return <Bot className="size-3 shrink-0" />;
case "device":
Expand Down Expand Up @@ -801,16 +800,17 @@ function PullRequestSurfaceIcon({
},
}),
).data;
// Only state and draft reach the tab. A list seed cannot know mergeability, so feeding the
// full detail would flip an open tab to the conflict glyph the moment its read lands.
// The compact tab intentionally shows lifecycle and draft state only. Conflict warnings have
// their own presentation on surfaces that have mergeability, while this tab stays stable as
// detail data arrives.
const status =
linkedSnapshot !== null
? linkedSnapshot
: detail === null
? (seed ?? null)
: { state: detail.state, isDraft: detail.isDraft };
if (status === null) {
return <GitPullRequest className="size-3 shrink-0 text-muted-foreground" />;
return <PullRequestGlyph.pullRequest className="size-3 shrink-0 text-muted-foreground" />;
}
const presentation = resolvePullRequestState({ state: status.state, isDraft: status.isDraft });
return <presentation.Icon className={cn("size-3 shrink-0", presentation.toneClassName)} />;
Expand Down Expand Up @@ -895,15 +895,15 @@ export function RightPanelTabs(props: RightPanelTabsProps) {
},
{
label: "Pull request",
icon: GitPullRequest,
icon: PullRequestGlyph.pullRequest,
shortcut: "P",
available: props.pullRequestAvailable,
disabledReason: SURFACE_DISABLED_REASONS.pullRequest,
onClick: props.onAddPullRequest,
},
{
label: "Linked pull requests",
icon: GitPullRequestArrow,
icon: PullRequestGlyph.link,
shortcut: "L",
available: props.pullRequestsAvailable,
disabledReason: SURFACE_DISABLED_REASONS.pullRequests,
Expand Down
152 changes: 130 additions & 22 deletions apps/web/src/components/ThreadStatusIndicators.test.ts
Original file line number Diff line number Diff line change
@@ -1,25 +1,20 @@
import { ProjectId, type PullRequestSummary, type VcsStatusResult } from "@t3tools/contracts";
import { describe, expect, it } from "@effect/vitest";
import {
GitMergeIcon,
GitPullRequestClosedIcon,
GitPullRequestDraftIcon,
GitPullRequestIcon,
} from "lucide-react";

import {
ChangeRequestStatusIcon,
prStatusIndicator,
settledPrHoverColorClass,
resolveThreadPullRequestBadgePresentation,
} from "./ThreadStatusIndicators";
import { newestPullRequestSummary } from "../state/pullRequests";
import { PullRequestGlyph } from "~/components/pullRequest/pullRequestIcons";

describe("ChangeRequestStatusIcon", () => {
it.each([
["open", "open", false, GitPullRequestIcon],
["draft", "open", true, GitPullRequestDraftIcon],
["closed", "closed", false, GitPullRequestClosedIcon],
["merged", "merged", false, GitMergeIcon],
["open", "open", false, PullRequestGlyph.pullRequest],
["draft", "open", true, PullRequestGlyph.draft],
["closed", "closed", false, PullRequestGlyph.closed],
["merged", "merged", false, PullRequestGlyph.merged],
] as const)("uses the %s pull request glyph", (_label, state, isDraft, expectedIcon) => {
expect(ChangeRequestStatusIcon({ state, isDraft }).type).toBe(expectedIcon);
});
Expand Down Expand Up @@ -119,18 +114,131 @@ describe("prStatusIndicator", () => {
});
});

describe("settledPrHoverColorClass", () => {
it.each([
["open", "text-emerald-600"],
["merged", "text-violet-600"],
["closed", "text-red-600"],
] as const)("restores the %s pull request color on row hover", (state, colorClass) => {
expect(settledPrHoverColorClass(state)).toContain(`group-hover/sidebar-row:${colorClass}`);
describe("resolveThreadPullRequestBadgePresentation", () => {
const url = "https://github.com/pingdotgg/t3code/pull/42";

it("returns the pending pull-request badge when no snapshot is available", () => {
expect(
resolveThreadPullRequestBadgePresentation({
badge: null,
number: 42,
url,
status: null,
}),
).toEqual({
Icon: PullRequestGlyph.pullRequest,
toneClassName: "text-muted-foreground",
label: "PR #42, status pending",
text: 42,
});
});

it("keeps draft pull requests gray on row hover", () => {
expect(settledPrHoverColorClass("open", true)).toContain(
"group-hover/sidebar-row:text-zinc-500",
);
it.each([
[
"open",
{ state: "open", isDraft: false },
PullRequestGlyph.pullRequest,
"text-emerald-600 dark:text-emerald-300/90",
"PR #42 - Open: PR branch",
],
[
"draft",
{ state: "open", isDraft: true },
PullRequestGlyph.draft,
"text-zinc-500 dark:text-zinc-400/80",
"PR #42 - Draft: PR branch",
],
[
"closed",
{ state: "closed", isDraft: false },
PullRequestGlyph.closed,
"text-red-600 dark:text-red-300/90",
"PR #42 - Closed: PR branch",
],
[
"merged",
{ state: "merged", isDraft: false },
PullRequestGlyph.merged,
"text-violet-600 dark:text-violet-300/90",
"PR #42 - Merged: PR branch",
],
] as const)(
"keeps the %s state for one linked pull request",
(_state, prOverrides, expectedIcon, expectedToneClassName, expectedLabel) => {
const fixture = status().pr;
if (!fixture) throw new Error("Expected pull request fixture");
const prStatus = prStatusIndicator({ ...fixture, ...prOverrides }, undefined);
if (!prStatus) throw new Error("Expected pull request status");

expect(
resolveThreadPullRequestBadgePresentation({
badge: { kind: "pull-request", others: 0, state: "open" },
number: fixture.number,
url: fixture.url,
status: prStatus,
}),
).toEqual({
Icon: expectedIcon,
toneClassName: expectedToneClassName,
label: expectedLabel,
text: fixture.number,
});
},
);

it.each([
["open", "text-emerald-600 dark:text-emerald-300/90"],
["draft", "text-zinc-500 dark:text-zinc-400/80"],
["merged", "text-violet-600 dark:text-violet-300/90"],
] as const)(
"uses a layers badge with the %s stack tone without a link identity",
(state, expectedToneClassName) => {
expect(
resolveThreadPullRequestBadgePresentation({
badge: { kind: "stack", layers: 3, state },
status: null,
}),
).toEqual({
Icon: PullRequestGlyph.stack,
toneClassName: expectedToneClassName,
label: `Stack of 3 pull requests, ${state}`,
text: 3,
});
},
);

it.each([
["open", PullRequestGlyph.pullRequest, "text-emerald-600 dark:text-emerald-300/90"],
["draft", PullRequestGlyph.draft, "text-zinc-500 dark:text-zinc-400/80"],
["merged", PullRequestGlyph.merged, "text-violet-600 dark:text-violet-300/90"],
] as const)(
"draws the count of unrelated linked pull requests with their %s aggregate state",
(state, expectedIcon, expectedToneClassName) => {
const fixture = status().pr;
if (!fixture) throw new Error("Expected pull request fixture");
const closedStatus = prStatusIndicator(
{ ...fixture, state: "closed", isDraft: false },
undefined,
);
if (!closedStatus) throw new Error("Expected pull request status");

expect(
resolveThreadPullRequestBadgePresentation({
badge: { kind: "pull-request", others: 2, state },
number: fixture.number,
url: fixture.url,
status: closedStatus,
}),
).toEqual({
Icon: expectedIcon,
toneClassName: expectedToneClassName,
label: `PR #42 - Closed: PR branch, and 2 more linked; overall ${state}`,
text: "+3",
});
},
);

it("omits the control when neither a stack nor a linked identity can be shown", () => {
expect(resolveThreadPullRequestBadgePresentation({ badge: null, status: null })).toBeNull();
});
});
Loading
Loading