feat(app): lift bulk archive above the subagents list

The archive-finished action sat at the foot of the track panel with nothing
between it and the subagent rows, so it read as one more subagent.

It now sits above the list behind a divider. The pointer enters the panel from
the pill below it, so the top edge is the far end of the trip: reaching a bulk
action costs a pass over every row it affects, and it can't land under a thumb
that was aiming for the nearest subagent.

It stays a row rather than becoming a Button. The panel is a menu surface, and
a bordered control inside a list of menu items reads as a foreign object — the
divider does the separating, not the chrome.
This commit is contained in:
Mohamed Boudra
2026-08-18 09:17:34 +02:00
parent f9633dd359
commit b880a70e73
2 changed files with 31 additions and 11 deletions
+19
View File
@@ -3,6 +3,7 @@ import { Pressable, Text, View } from "react-native";
import { StyleSheet } from "react-native-unistyles";
import {
MenuRoot,
MenuSeparator,
MenuSurface,
MenuTrigger,
useMenuContext,
@@ -146,6 +147,24 @@ function ComposerTrackPillTrigger({
);
}
/**
* Actions that apply to the whole panel rather than one row — bulk archive today. Still rows:
* they live on a menu surface, and a bordered button inside a list of menu items reads as a
* foreign object. The divider is what separates them from the list, not their chrome.
*
* They go above the rows because the pointer arrives from the pill below. Reaching a bulk action
* means travelling past every row it affects, which is the right price for it, and it cannot sit
* under a thumb that was aiming for the nearest subagent.
*/
export function ComposerTrackActions({ children }: { children: ReactNode }): ReactElement {
return (
<>
{children}
<MenuSeparator />
</>
);
}
export interface ComposerTrackRowProps {
/** A function child receives the row's own hover/press state, for hover-revealed actions. */
children: ReactNode | ((state: { active: boolean }) => ReactNode);
+12 -11
View File
@@ -4,7 +4,7 @@ import { useTranslation } from "react-i18next";
import { Archive, Unlink } from "lucide-react-native";
import { StyleSheet, withUnistyles } from "react-native-unistyles";
import { getProviderIcon } from "@/components/provider-icons";
import { ComposerTrackPill, ComposerTrackRow } from "@/composer/tracks";
import { ComposerTrackActions, ComposerTrackPill, ComposerTrackRow } from "@/composer/tracks";
import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip";
import { useIsCompactFormFactor } from "@/constants/layout";
import { isNative } from "@/constants/platform";
@@ -82,6 +82,15 @@ export function SubagentsTrack({
panelTitle={t("subagents.title")}
statusBucket={pill.statusBucket}
>
{showArchiveFinished && onArchiveFinished ? (
<ComposerTrackActions>
<ArchiveFinishedRow
status={archiveFinishedStatus}
disabled={isArchivingFinished}
onPress={onArchiveFinished}
/>
</ComposerTrackActions>
) : null}
{rows.map((row) => (
<SubagentsTrackRow
key={row.id}
@@ -92,21 +101,13 @@ export function SubagentsTrack({
onDetachSubagent={onDetachSubagent}
/>
))}
{showArchiveFinished && onArchiveFinished ? (
<ArchiveFinishedRow
status={archiveFinishedStatus}
disabled={isArchivingFinished}
onPress={onArchiveFinished}
/>
) : null}
</ComposerTrackPill>
);
}
/**
* Bulk archive, as a row at the foot of the panel rather than an icon next to the count. The
* pill has no header to hang an icon off, and a destructive-ish action reads better with its
* name attached.
* Bulk archive, as a row above the list rather than an icon next to the count. The pill has no
* header to hang an icon off, and a destructive-ish action reads better with its name attached.
*/
function ArchiveFinishedRow({
status,