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
9 changes: 1 addition & 8 deletions apps/web/src/components/Sidebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3371,7 +3371,6 @@ export default function Sidebar({ routeReveal }: { routeReveal: SidebarRouteReve
className="mt-auto"
label="Working"
count={workingThreads.length}
hostsUndoNotice
toggle={{
expanded: workingShelfExpanded,
onToggle: toggleWorkingShelf,
Expand All @@ -3387,7 +3386,6 @@ export default function Sidebar({ routeReveal }: { routeReveal: SidebarRouteReve
className={cn(workingThreads.length === 0 && "mt-auto")}
label="Snoozed"
count={snoozedThreads.length}
hostsUndoNotice={workingThreads.length === 0}
toggle={{
expanded: snoozedShelfExpanded,
onToggle: toggleSnoozedShelf,
Expand All @@ -3405,9 +3403,6 @@ export default function Sidebar({ routeReveal }: { routeReveal: SidebarRouteReve
)}
label="Settled"
count={settledCount}
hostsUndoNotice={
workingThreads.length + snoozedThreads.length === 0
}
dragging={from !== null}
isDropTarget={dragTargetSection === "settled"}
toggle={{
Expand Down Expand Up @@ -3502,9 +3497,7 @@ export default function Sidebar({ routeReveal }: { routeReveal: SidebarRouteReve
) : null}
</SidebarGroup>
</SidebarContent>
<SidebarChromeFooter
showUndoNotice={isSearchingThreads || sidebarListItemsFresh.length === 0}
/>
<SidebarChromeFooter />
</>
);
}
8 changes: 2 additions & 6 deletions apps/web/src/components/sidebar/SidebarChrome.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -223,19 +223,15 @@ const SidebarUtilityMenu = memo(function SidebarUtilityMenu() {
/>
</>
)}
<SidebarThreadUndoNotice />
<SidebarUpdatePill />
</SidebarMenu>
);
});

export const SidebarChromeFooter = memo(function SidebarChromeFooter({
showUndoNotice = false,
}: {
showUndoNotice?: boolean;
}) {
export const SidebarChromeFooter = memo(function SidebarChromeFooter() {
return (
<SidebarFooter>
{showUndoNotice ? <SidebarThreadUndoNotice /> : null}
<SidebarProviderUpdatePill />
<SidebarUpdateArchitectureWarning />
<SidebarUtilityMenu />
Expand Down
6 changes: 0 additions & 6 deletions apps/web/src/components/sidebar/SidebarListMarkers.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ import {
type SidebarListMarker,
} from "../Sidebar.logic";
import { SIDEBAR_SORTABLE_DRAG_DISABLED } from "./SidebarThreadRow";
import { SidebarThreadUndoNotice } from "./SidebarThreadUndoNotice";

function SortableSidebarMarker(props: {
marker: SidebarListMarker;
Expand Down Expand Up @@ -117,8 +116,6 @@ export function SidebarSectionHeader(props: {
dragging?: boolean;
isDropTarget?: boolean;
toggle: { expanded: boolean; onToggle: () => void };
/** The first shelf at the bottom of the list hosts the undo notice just above itself. */
hostsUndoNotice?: boolean;
}) {
const shelf =
props.marker === "working-header"
Expand All @@ -132,9 +129,6 @@ export function SidebarSectionHeader(props: {
data-testid={`sidebar-${props.marker}`}
className={cn("relative mx-0.5 h-8", props.className)}
>
{props.hostsUndoNotice ? (
<SidebarThreadUndoNotice className="absolute inset-x-0 bottom-full bg-sidebar surface-grain" />
) : null}
<CollapsibleSectionHeader
role="treeitem"
tabIndex={-1}
Expand Down
46 changes: 27 additions & 19 deletions apps/web/src/components/sidebar/SidebarThreadUndoNotice.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,24 @@ import { useState } from "react";

import {
threadUndoLabel,
threadUndoNoticeSummary,
undoLatestThreadAction,
useThreadUndoNotice,
} from "../../hooks/showThreadUndoNotice";
import { cn } from "../../lib/utils";
import { primaryServerKeybindingsAtom } from "../../state/server";
import { InlineButton } from "../ui/button";
import { SidebarMenuItem } from "../ui/sidebar";

export function SidebarThreadUndoNotice({ className }: { className?: string }) {
/** Fills the free space in the sidebar footer's utility row, so it never moves or covers the list. */
export function SidebarThreadUndoNotice() {
const notice = useThreadUndoNotice((state) => state.notice);
const keybindings = useAtomValue(primaryServerKeybindingsAtom);
const summary = notice ? threadUndoNoticeSummary(notice) : null;
// Short enough to fit beside the utility icons; screen readers hear the full summary.
const summary = notice
? notice.count === 1
? notice.action
: `${notice.action} ${notice.count}`
: null;

// Keeps the last text on screen while it fades out, then unmounts on transitionend.
const [shownSummary, setShownSummary] = useState(summary);
Expand All @@ -23,21 +29,23 @@ export function SidebarThreadUndoNotice({ className }: { className?: string }) {
if (!text) return null;

return (
<div
role="group"
aria-hidden={!summary}
inert={!summary}
onTransitionEnd={(event) => {
if (!summary && event.target === event.currentTarget) setShownSummary(null);
}}
className={cn(
"flex gap-1 px-2 text-2xs leading-6 text-sidebar-muted-foreground transition-opacity duration-200 ease-out starting:opacity-0",
!summary && "opacity-0",
className,
)}
>
<span className="min-w-0 truncate">{text},</span>
<InlineButton onClick={undoLatestThreadAction}>{threadUndoLabel(keybindings)}</InlineButton>
</div>
// No min-w-0: the undo button sets this item's minimum, so a shared row squeezes its neighbour.
<SidebarMenuItem className="flex flex-1" aria-hidden={!summary} inert={!summary}>
<div
onTransitionEnd={(event) => {
if (!summary && event.target === event.currentTarget) setShownSummary(null);
}}
// Reversed wrap: a summary that does not fit beside the undo button drops to a
// second line that overflow hides, instead of truncating mid-word. Browse mode
// reads the button first; the live announcement carries the full sentence.
className={cn(
"flex h-6 min-w-0 flex-1 flex-row-reverse flex-wrap gap-x-1 overflow-hidden text-2xs leading-6 whitespace-nowrap text-sidebar-muted-foreground transition-opacity duration-200 ease-out starting:opacity-0",
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Resolved
!summary && "opacity-0",
)}
>
<InlineButton onClick={undoLatestThreadAction}>{threadUndoLabel(keybindings)}</InlineButton>
<span>{text},</span>
</div>
</SidebarMenuItem>
);
}
2 changes: 1 addition & 1 deletion apps/web/src/hooks/showThreadUndoNotice.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ type UndoNotice = {

export const useThreadUndoNotice = create<{ notice: UndoNotice | null }>(() => ({ notice: null }));

export function threadUndoNoticeSummary(notice: Pick<UndoNotice, "action" | "count">): string {
function threadUndoNoticeSummary(notice: Pick<UndoNotice, "action" | "count">): string {
const noun = notice.action === "Discarded" ? "draft" : "thread";
return `${notice.action} ${notice.count} ${noun}${notice.count === 1 ? "" : "s"}`;
}
Expand Down
Loading