authorgravatar for jappyjan@users.noreply.github.comJan Jaap <jappyjan@users.noreply.github.com> 2026-04-30 20:46:05+02:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2026-04-30 18:46:05+00:00
log532a1d891db03276764d2528b30a0e6f6c396b6a
treee202d61837b3432a169cd0d06599e7cc2bb9445e
parent9e2f7129e3bce9058294051eb6f99d2efc6a8e86
signature Signed by PGP key B5690EEEBB952194

fix(web): make thread archive button always visible on mobile (#2423)

Co-authored-by: Claude Sonnet 4.7 <noreply@anthropic.com> Co-authored-by: Julius Marminge <julius0216@outlook.com>

3 files changed, 65 insertions(+), 19 deletions(-)

apps/web/src/components/Sidebar.tsx+19-11
......@@ -373,7 +373,7 @@ const SidebarThreadRow = memo(function SidebarThreadRow(props: SidebarThreadRowP
373373 const threadMetaClassName = isConfirmingArchive
374374 ? "pointer-events-none opacity-0"
375375 : !isThreadRunning
376 ? "pointer-events-none transition-opacity duration-150 group-hover/menu-sub-item:opacity-0 group-focus-within/menu-sub-item:opacity-0"
376 ? "pointer-events-none transition-opacity duration-150 max-sm:pr-6 group-hover/menu-sub-item:opacity-0 group-focus-within/menu-sub-item:opacity-0"
377377 : "pointer-events-none";
378378 const clearConfirmingArchive = useCallback(() => {
379379 setConfirmingArchiveThreadKey((current) => (current === threadKey ? null : current));
......@@ -605,7 +605,11 @@ const SidebarThreadRow = memo(function SidebarThreadRow(props: SidebarThreadRowP
605605 <TerminalIcon className={`size-3 ${terminalStatus.pulse ? "animate-pulse" : ""}`} />
606606 </span>
607607 )}
608 <div className="flex min-w-12 justify-end">
608 <div
609 className={`flex min-w-12 justify-end ${
610 isRemoteThread ? "max-sm:min-w-24" : "max-sm:min-w-20"
611 }`}
612 >
609613 {isConfirmingArchive ? (
610614 <button
611615 ref={handleConfirmArchiveRef}
......@@ -621,13 +625,13 @@ const SidebarThreadRow = memo(function SidebarThreadRow(props: SidebarThreadRowP
621625 </button>
622626 ) : !isThreadRunning ? (
623627 appSettingsConfirmThreadArchive ? (
624 <div className="pointer-events-none absolute top-1/2 right-1 -translate-y-1/2 opacity-0 transition-opacity duration-150 group-hover/menu-sub-item:pointer-events-auto group-hover/menu-sub-item:opacity-100 group-focus-within/menu-sub-item:pointer-events-auto group-focus-within/menu-sub-item:opacity-100">
628 <div className="pointer-events-none absolute top-1/2 right-1 -translate-y-1/2 opacity-0 transition-opacity duration-150 max-sm:pointer-events-auto max-sm:opacity-100 group-hover/menu-sub-item:pointer-events-auto group-hover/menu-sub-item:opacity-100 group-focus-within/menu-sub-item:pointer-events-auto group-focus-within/menu-sub-item:opacity-100">
625629 <button
626630 type="button"
627631 data-thread-selection-safe
628632 data-testid={`thread-archive-${thread.id}`}
629633 aria-label={`Archive ${thread.title}`}
630 className="inline-flex size-5 cursor-pointer items-center justify-center text-muted-foreground transition-colors hover:text-foreground focus-visible:outline-hidden focus-visible:ring-1 focus-visible:ring-ring"
634 className="inline-flex size-5 cursor-pointer items-center justify-center text-muted-foreground/60 transition-colors hover:text-foreground focus-visible:outline-hidden focus-visible:ring-1 focus-visible:ring-ring"
631635 onPointerDown={stopPropagationOnPointerDown}
632636 onClick={handleStartArchiveConfirmation}
633637 >
......@@ -638,13 +642,13 @@ const SidebarThreadRow = memo(function SidebarThreadRow(props: SidebarThreadRowP
638642 <Tooltip>
639643 <TooltipTrigger
640644 render={
641 <div className="pointer-events-none absolute top-1/2 right-1 -translate-y-1/2 opacity-0 transition-opacity duration-150 group-hover/menu-sub-item:pointer-events-auto group-hover/menu-sub-item:opacity-100 group-focus-within/menu-sub-item:pointer-events-auto group-focus-within/menu-sub-item:opacity-100">
645 <div className="pointer-events-none absolute top-1/2 right-1 -translate-y-1/2 opacity-0 transition-opacity duration-150 max-sm:pointer-events-auto max-sm:opacity-100 group-hover/menu-sub-item:pointer-events-auto group-hover/menu-sub-item:opacity-100 group-focus-within/menu-sub-item:pointer-events-auto group-focus-within/menu-sub-item:opacity-100">
642646 <button
643647 type="button"
644648 data-thread-selection-safe
645649 data-testid={`thread-archive-${thread.id}`}
646650 aria-label={`Archive ${thread.title}`}
647 className="inline-flex size-5 cursor-pointer items-center justify-center text-muted-foreground transition-colors hover:text-foreground focus-visible:outline-hidden focus-visible:ring-1 focus-visible:ring-ring"
651 className="inline-flex size-5 cursor-pointer items-center justify-center text-muted-foreground/60 transition-colors hover:text-foreground focus-visible:outline-hidden focus-visible:ring-1 focus-visible:ring-ring"
648652 onPointerDown={stopPropagationOnPointerDown}
649653 onClick={handleArchiveImmediateClick}
650654 >
......@@ -665,11 +669,11 @@ const SidebarThreadRow = memo(function SidebarThreadRow(props: SidebarThreadRowP
665669 render={
666670 <span
667671 aria-label={threadEnvironmentLabel ?? "Remote"}
668 className="inline-flex items-center justify-center"
672 className="inline-flex h-5 items-center justify-center"
669673 />
670674 }
671675 >
672 <CloudIcon className="size-3 text-muted-foreground/40" />
676 <CloudIcon className="block size-3 text-muted-foreground/60" />
673677 </TooltipTrigger>
674678 <TooltipPopup side="top">{threadEnvironmentLabel}</TooltipPopup>
675679 </Tooltip>
......@@ -2025,7 +2029,7 @@ const SidebarProjectItem = memo(function SidebarProjectItem(props: SidebarProjec
20252029 ? "Remote project"
20262030 : "Available in multiple environments"
20272031 }
2028 className="pointer-events-none absolute top-1 right-1.5 inline-flex size-5 items-center justify-center rounded-md text-muted-foreground/50 transition-opacity duration-150 max-sm:right-7 group-hover/project-header:opacity-0 group-focus-within/project-header:opacity-0 max-sm:group-hover/project-header:opacity-100 max-sm:group-focus-within/project-header:opacity-100"
2032 className="pointer-events-none absolute top-1 right-1.5 inline-flex size-5 items-center justify-center rounded-md text-muted-foreground/60 transition-opacity duration-150 max-sm:right-7 group-hover/project-header:opacity-0 group-focus-within/project-header:opacity-0 max-sm:group-hover/project-header:opacity-100 max-sm:group-focus-within/project-header:opacity-100"
20292033 />
20302034 }
20312035 >
......@@ -2044,7 +2048,7 @@ const SidebarProjectItem = memo(function SidebarProjectItem(props: SidebarProjec
20442048 type="button"
20452049 aria-label={`Create new thread in ${project.displayName}`}
20462050 data-testid="new-thread-button"
2047 className="inline-flex size-5 cursor-pointer items-center justify-center rounded-md text-muted-foreground/70 hover:bg-secondary hover:text-foreground focus-visible:outline-hidden focus-visible:ring-1 focus-visible:ring-ring"
2051 className="inline-flex size-5 cursor-pointer items-center justify-center rounded-md text-muted-foreground/60 hover:bg-secondary hover:text-foreground focus-visible:outline-hidden focus-visible:ring-1 focus-visible:ring-ring"
20482052 onClick={handleCreateThreadClick}
20492053 >
20502054 <SquarePenIcon className="size-3.5" />
......@@ -2420,9 +2424,13 @@ const SidebarChromeHeader = memo(function SidebarChromeHeader({
24202424
24212425const SidebarChromeFooter = memo(function SidebarChromeFooter() {
24222426 const navigate = useNavigate();
2427 const { isMobile, setOpenMobile } = useSidebar();
24232428 const handleSettingsClick = useCallback(() => {
2429 if (isMobile) {
2430 setOpenMobile(false);
2431 }
24242432 void navigate({ to: "/settings" });
2425 }, [navigate]);
2433 }, [isMobile, navigate, setOpenMobile]);
24262434
24272435 return (
24282436 <SidebarFooter className="p-2">
apps/web/src/components/settings/SettingsSidebarNav.tsx+26-4
......@@ -1,6 +1,6 @@
1import type { ComponentType } from "react";
1import { useCallback, type ComponentType } from "react";
22import { ArchiveIcon, ArrowLeftIcon, Link2Icon, Settings2Icon } from "lucide-react";
3import { useNavigate } from "@tanstack/react-router";
3import { useCanGoBack, useNavigate } from "@tanstack/react-router";
44
55import {
66 SidebarContent,
......@@ -10,6 +10,7 @@ import {
1010 SidebarMenuButton,
1111 SidebarMenuItem,
1212 SidebarSeparator,
13 useSidebar,
1314} from "../ui/sidebar";
1415
1516export type SettingsSectionPath =
......@@ -29,6 +30,27 @@ export const SETTINGS_NAV_ITEMS: ReadonlyArray<{
2930
3031export function SettingsSidebarNav({ pathname }: { pathname: string }) {
3132 const navigate = useNavigate();
33 const canGoBack = useCanGoBack();
34 const { isMobile, setOpenMobile } = useSidebar();
35 const handleSectionClick = useCallback(
36 (to: SettingsSectionPath) => {
37 if (isMobile) {
38 setOpenMobile(false);
39 }
40 void navigate({ to, replace: true });
41 },
42 [isMobile, navigate, setOpenMobile],
43 );
44 const handleBackClick = useCallback(() => {
45 if (isMobile) {
46 setOpenMobile(false);
47 }
48 if (canGoBack) {
49 window.history.back();
50 return;
51 }
52 void navigate({ to: "/" });
53 }, [canGoBack, isMobile, navigate, setOpenMobile]);
3254
3355 return (
3456 <>
......@@ -48,7 +70,7 @@ export function SettingsSidebarNav({ pathname }: { pathname: string }) {
4870 ? "gap-2.5 px-2.5 py-2 text-left text-[13px] font-medium text-foreground"
4971 : "gap-2.5 px-2.5 py-2 text-left text-[13px] text-muted-foreground/70 hover:text-foreground/80"
5072 }
51 onClick={() => void navigate({ to: item.to, replace: true })}
73 onClick={() => handleSectionClick(item.to)}
5274 >
5375 <Icon
5476 className={
......@@ -73,7 +95,7 @@ export function SettingsSidebarNav({ pathname }: { pathname: string }) {
7395 <SidebarMenuButton
7496 size="sm"
7597 className="gap-2 px-2 py-2 text-xs text-muted-foreground hover:bg-accent hover:text-foreground"
76 onClick={() => window.history.back()}
98 onClick={handleBackClick}
7799 >
78100 <ArrowLeftIcon className="size-4" />
79101 <span>Back</span>
apps/web/src/routes/settings.tsx+20-4
......@@ -1,6 +1,13 @@
11import { RotateCcwIcon } from "lucide-react";
2import { Outlet, createFileRoute, redirect, useLocation } from "@tanstack/react-router";
3import { useEffect, useState } from "react";
2import {
3 Outlet,
4 createFileRoute,
5 redirect,
6 useCanGoBack,
7 useLocation,
8 useNavigate,
9} from "@tanstack/react-router";
10import { useCallback, useEffect, useState } from "react";
411
512import { useSettingsRestore } from "../components/settings/SettingsPanels";
613import { Button } from "../components/ui/button";
......@@ -25,16 +32,25 @@ function RestoreDefaultsButton({ onRestored }: { onRestored: () => void }) {
2532
2633function SettingsContentLayout() {
2734 const location = useLocation();
35 const navigate = useNavigate();
36 const canGoBack = useCanGoBack();
2837 const [restoreSignal, setRestoreSignal] = useState(0);
2938 const showRestoreDefaults = location.pathname === "/settings/general";
3039 const handleRestored = () => setRestoreSignal((value) => value + 1);
40 const navigateBackWithinApp = useCallback(() => {
41 if (canGoBack) {
42 window.history.back();
43 return;
44 }
45 void navigate({ to: "/" });
46 }, [canGoBack, navigate]);
3147
3248 useEffect(() => {
3349 const onKeyDown = (event: KeyboardEvent) => {
3450 if (event.defaultPrevented) return;
3551 if (event.key === "Escape") {
3652 event.preventDefault();
37 window.history.back();
53 navigateBackWithinApp();
3854 }
3955 };
4056
......@@ -42,7 +58,7 @@ function SettingsContentLayout() {
4258 return () => {
4359 window.removeEventListener("keydown", onKeyDown);
4460 };
45 }, []);
61 }, [navigateBackWithinApp]);
4662
4763 return (
4864 <SidebarInset className="h-dvh min-h-0 overflow-hidden overscroll-y-none bg-background text-foreground isolate">