authorgravatar for julius0216@outlook.comJulius Marminge <julius0216@outlook.com> 2026-09-10 11:34:55-07:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2026-09-10 11:34:55-07:00
log0527ddf06defc88169c245a0fe53635d41d66469
tree78065c97c8f2c63fd9aa08b5bb2bda4cd2e45b57
parent0882431e0fc0fabe950743dd44b674a07a584bc9
signature Signed by PGP key B5690EEEBB952194

fix: quiet settled threads and simplify PR badges (#11101)


6 files changed, 26 insertions(+), 23 deletions(-)

apps/mobile/src/features/threads/thread-list-items.tsx+2-2
...@@ -44,11 +44,11 @@ export const THREAD_LIST_COMPACT_INSET = HOME_HORIZONTAL_INSET;...@@ -44,11 +44,11 @@ export const THREAD_LIST_COMPACT_INSET = HOME_HORIZONTAL_INSET;
44const SIDEBAR_ROW_RADIUS = 12;44const SIDEBAR_ROW_RADIUS = 12;
4545
46function pullRequestTintColor(46function pullRequestTintColor(
47 pr: Pick<ThreadPrPresentation, "state" | "isDraft">,47 pr: Pick<ThreadPrPresentation, "state" | "isDraft" | "others">,
48 colorScheme: "light" | "dark",48 colorScheme: "light" | "dark",
49) {49) {
50 const dark = colorScheme === "dark";50 const dark = colorScheme === "dark";
51 if (pr.state === "open" && pr.isDraft === true) {51 if (pr.others > 0 || (pr.state === "open" && pr.isDraft === true)) {
52 return dark ? "#a1a1aa" : "#71717a";52 return dark ? "#a1a1aa" : "#71717a";
53 }53 }
54 switch (pr.state) {54 switch (pr.state) {
apps/mobile/src/features/threads/thread-list-v2-items.tsx+1-7
...@@ -880,13 +880,7 @@ export const ThreadListV2Row = memo(function ThreadListV2Row(props: {...@@ -880,13 +880,7 @@ export const ThreadListV2Row = memo(function ThreadListV2Row(props: {
880 ? materialYouStyleLayoutActive880 ? materialYouStyleLayoutActive
881 ? "accent-thread-selected-foreground"881 ? "accent-thread-selected-foreground"
882 : "accent-user-bubble-foreground"882 : "accent-user-bubble-foreground"
883 : pr.kind === "stack" || pr.isDraft || pr.state === null883 : "accent-foreground-muted"
884 ? "accent-foreground-muted"
885 : pr.state === "open"
886 ? "accent-adaptive-emerald-600-400"
887 : pr.state === "merged"
888 ? "accent-adaptive-violet-600-400"
889 : "accent-foreground-muted"
890 }884 }
891 />885 />
892 ) : null}886 ) : null}
apps/mobile/src/state/thread-pr-presentation.ts+7-3
...@@ -65,11 +65,12 @@ export function presentThreadLinkedPullRequests(...@@ -65,11 +65,12 @@ export function presentThreadLinkedPullRequests(
65 const snapshot = link.snapshot;65 const snapshot = link.snapshot;
66 const state = badge.kind === "stack" ? badge.state : (snapshot?.state ?? null);66 const state = badge.kind === "stack" ? badge.state : (snapshot?.state ?? null);
67 const isDraft = snapshot?.isDraft === true && state === "open";67 const isDraft = snapshot?.isDraft === true && state === "open";
68 const linkedCount = badge.kind === "pull-request" && badge.others > 0 ? badge.others + 1 : null;
68 const label =69 const label =
69 badge.kind === "stack"70 badge.kind === "stack"
70 ? String(badge.layers)71 ? String(badge.layers)
71 : badge.others > 072 : linkedCount !== null
72 ? `+${badge.others}`73 ? `+${linkedCount}`
73 : String(link.number);74 : String(link.number);
74 return {75 return {
75 kind: badge.kind,76 kind: badge.kind,
...@@ -84,7 +85,10 @@ export function presentThreadLinkedPullRequests(...@@ -84,7 +85,10 @@ export function presentThreadLinkedPullRequests(
84 badge.kind === "stack"85 badge.kind === "stack"
85 ? `${badge.layers} pull requests in stack, ${state ?? "status pending"}`86 ? `${badge.layers} pull requests in stack, ${state ?? "status pending"}`
86 : `#${link.number} pull request ${state === null ? "status pending" : isDraft ? "draft" : state}${badge.others > 0 ? `, ${badge.others} more linked` : ""}`,87 : `#${link.number} pull request ${state === null ? "status pending" : isDraft ? "draft" : state}${badge.others > 0 ? `, ${badge.others} more linked` : ""}`,
87 textClassName: state === null || isDraft ? "text-foreground-muted" : PR_STATE_TEXT_CLASS[state],88 textClassName:
89 linkedCount !== null || state === null || isDraft
90 ? "text-foreground-muted"
91 : PR_STATE_TEXT_CLASS[state],
88 };92 };
89}93}
9094
apps/mobile/src/state/use-thread-pr.test.ts+3-1
...@@ -87,7 +87,9 @@ describe("presentThreadLinkedPullRequests", () => {...@@ -87,7 +87,9 @@ describe("presentThreadLinkedPullRequests", () => {
87 it("counts unrelated links without labelling them a stack", () => {87 it("counts unrelated links without labelling them a stack", () => {
88 expect(presentThreadLinkedPullRequests([linkedPr(1), linkedPr(2)])).toMatchObject({88 expect(presentThreadLinkedPullRequests([linkedPr(1), linkedPr(2)])).toMatchObject({
89 kind: "pull-request",89 kind: "pull-request",
90 label: "+1",90 label: "+2",
91 others: 1,
92 textClassName: "text-foreground-muted",
91 });93 });
92 });94 });
9395
apps/web/src/components/Sidebar.tsx+6-4
...@@ -1387,6 +1387,7 @@ const SidebarThreadRow = memo(function SidebarThreadRow(props: {...@@ -1387,6 +1387,7 @@ const SidebarThreadRow = memo(function SidebarThreadRow(props: {
1387 // content; surface is reserved for interaction (hover, multi-select, route).1387 // content; surface is reserved for interaction (hover, multi-select, route).
1388 const rowSurfaceClassName = cn(1388 const rowSurfaceClassName = cn(
1389 "group/sidebar-row relative w-full cursor-pointer overflow-hidden rounded-md text-left outline-none select-none",1389 "group/sidebar-row relative w-full cursor-pointer overflow-hidden rounded-md text-left outline-none select-none",
1390 variantAction === "unsettle" && "[&:not(:hover):not(:focus-within)_*]:text-secondary-label/70",
1390 props.isActive1391 props.isActive
1391 ? "bg-sidebar-row-active text-sidebar-foreground"1392 ? "bg-sidebar-row-active text-sidebar-foreground"
1392 : isSelected1393 : isSelected
...@@ -1469,7 +1470,7 @@ const SidebarThreadRow = memo(function SidebarThreadRow(props: {...@@ -1469,7 +1470,7 @@ const SidebarThreadRow = memo(function SidebarThreadRow(props: {
1469 : "text-foreground/90",1470 : "text-foreground/90",
1470 )1471 )
1471 : cn(1472 : cn(
1472 "truncate group-hover/sidebar-row:text-foreground",1473 "truncate group-focus-within/sidebar-row:text-foreground group-hover/sidebar-row:text-foreground",
1473 shouldRecede1474 shouldRecede
1474 ? "text-secondary-label/70"1475 ? "text-secondary-label/70"
1475 : props.isActive || isWoke1476 : props.isActive || isWoke
...@@ -1485,7 +1486,7 @@ const SidebarThreadRow = memo(function SidebarThreadRow(props: {...@@ -1485,7 +1486,7 @@ const SidebarThreadRow = memo(function SidebarThreadRow(props: {
1485 </span>1486 </span>
1486 );1487 );
14871488
1488 // Stacks show their layer count; unrelated links show only the remainder count.1489 // Stacks show their layer count; multiple unrelated links show their total count.
1489 // Plain clicks open T3; individual PR links also support opening the host in a new tab.1490 // Plain clicks open T3; individual PR links also support opening the host in a new tab.
1490 const prBadgeShape = supportsMultiplePullRequests1491 const prBadgeShape = supportsMultiplePullRequests
1491 ? resolveThreadPullRequestBadge(thread.pullRequests)1492 ? resolveThreadPullRequestBadge(thread.pullRequests)
...@@ -1597,8 +1598,8 @@ const SidebarThreadRow = memo(function SidebarThreadRow(props: {...@@ -1597,8 +1598,8 @@ const SidebarThreadRow = memo(function SidebarThreadRow(props: {
1597 <span1598 <span
1598 className={cn(1599 className={cn(
1599 "shrink-0 transition-opacity",1600 "shrink-0 transition-opacity",
1600 !props.isActive &&1601 (!props.isActive || variantAction === "unsettle") &&
1601 "opacity-40 grayscale group-hover/sidebar-row:opacity-100 group-hover/sidebar-row:grayscale-0",1602 "opacity-40 grayscale group-focus-within/sidebar-row:opacity-100 group-focus-within/sidebar-row:grayscale-0 group-hover/sidebar-row:opacity-100 group-hover/sidebar-row:grayscale-0",
1602 )}1603 )}
1603 >1604 >
1604 {props.project ? <ProjectFavicon project={props.project} className="size-4" /> : null}1605 {props.project ? <ProjectFavicon project={props.project} className="size-4" /> : null}
...@@ -1617,6 +1618,7 @@ const SidebarThreadRow = memo(function SidebarThreadRow(props: {...@@ -1617,6 +1618,7 @@ const SidebarThreadRow = memo(function SidebarThreadRow(props: {
1617 the time/jump label yields to the settle affordance. */}1618 the time/jump label yields to the settle affordance. */}
1618 {prBadge}1619 {prBadge}
1619 {prBadge &&1620 {prBadge &&
1621 variantAction !== "unsettle" &&
1620 pr &&1622 pr &&
1621 (supportsMultiplePullRequests1623 (supportsMultiplePullRequests
1622 ? visibleThreadPullRequests(thread.pullRequests).length === 01624 ? visibleThreadPullRequests(thread.pullRequests).length === 0
apps/web/src/components/ThreadStatusIndicators.tsx+7-6
...@@ -154,6 +154,7 @@ export function ThreadPullRequestBadgeControl({...@@ -154,6 +154,7 @@ export function ThreadPullRequestBadgeControl({
154 onOpenPullRequest: (event: MouseEvent<HTMLAnchorElement>) => void;154 onOpenPullRequest: (event: MouseEvent<HTMLAnchorElement>) => void;
155}) {155}) {
156 const isStack = badge?.kind === "stack";156 const isStack = badge?.kind === "stack";
157 const linkedCount = badge?.kind === "pull-request" && badge.others > 0 ? badge.others + 1 : null;
157 if (!isStack && (number === undefined || url === undefined)) return null;158 if (!isStack && (number === undefined || url === undefined)) return null;
158 const label = isStack159 const label = isStack
159 ? `Stack of ${badge.layers} pull requests, ${badge.state}`160 ? `Stack of ${badge.layers} pull requests, ${badge.state}`
...@@ -169,16 +170,16 @@ export function ThreadPullRequestBadgeControl({...@@ -169,16 +170,16 @@ export function ThreadPullRequestBadgeControl({
169 "text-xs tabular-nums",170 "text-xs tabular-nums",
170 variant === "ghost" &&171 variant === "ghost" &&
171 "font-normal text-xs! active:scale-100 [--control-icon-color:currentColor]",172 "font-normal text-xs! active:scale-100 [--control-icon-color:currentColor]",
172 isStack ? PR_STATE_COLOR_CLASS[badge.state] : (status?.colorClass ?? "text-muted-foreground"),173 linkedCount !== null
174 ? "text-secondary-label"
175 : isStack
176 ? PR_STATE_COLOR_CLASS[badge.state]
177 : (status?.colorClass ?? "text-muted-foreground"),
173 );178 );
174 const content = (179 const content = (
175 <>180 <>
176 <ThreadPullRequestBadgeIcon icon={badge?.kind ?? "pull-request"} />181 <ThreadPullRequestBadgeIcon icon={badge?.kind ?? "pull-request"} />
177 {isStack182 {isStack ? badge.layers : linkedCount !== null ? `+${linkedCount}` : number}
178 ? badge.layers
179 : badge?.kind === "pull-request" && badge.others > 0
180 ? `+${badge.others}`
181 : number}
182 </>183 </>
183 );184 );
184 return (185 return (