Add pinnable new-tab targets to the tab row and sidebar

Any new-tab target (new agent, terminal, browser, or a terminal profile)
can be pinned as a quick-launch button. Pins are global client app state
(persisted), not daemon config, so the same set shows on every workspace;
profile pins resolve per host and are dropped where that profile is absent.

The sidebar mirror launches via a navigate-then-launch intent store rather
than calling the create handlers directly: terminal creation only works in
the mounted/focused workspace, so the sidebar requests the launch and the
workspace screen consumes it on focus.
This commit is contained in:
Mohamed Boudra
2026-06-15 14:09:29 +07:00
parent 42969e40d5
commit 713a042dc5
22 changed files with 835 additions and 65 deletions
@@ -70,6 +70,11 @@ import {
type SidebarWorkspaceEntry,
} from "@/hooks/use-sidebar-workspaces-list";
import { useSidebarOrderStore } from "@/stores/sidebar-order-store";
import { buildWorkspaceTabPersistenceKey } from "@/stores/workspace-tabs-store/state";
import { usePinnedLaunchers } from "@/workspace-pins/launch";
import { useLaunchIntentStore } from "@/workspace-pins/launch-intent-store";
import { PinnedTargetsRow } from "@/workspace-pins/pinned-targets-row";
import type { PinnedTabTarget } from "@/workspace-pins/target";
import { useShowShortcutBadges } from "@/hooks/use-show-shortcut-badges";
import { ContextMenuTrigger, useContextMenu } from "@/components/ui/context-menu";
import {
@@ -662,15 +667,40 @@ function WorkspaceRowRightGroup({
onRename?: () => void;
}) {
const { t } = useTranslation();
const isMobileBreakpoint = useIsCompactFormFactor();
const showShortcut = showShortcutBadge && shortcutNumber !== null;
const showKebab = Boolean(onArchive && (isHovered || isTouchPlatform));
const showKebabInSlot = showKebab && !showShortcut;
const shouldRenderActionSlot = Boolean(onArchive || workspace.diffStat);
const showPinnedTargets = (isHovered || platformIsNative || isMobileBreakpoint) && !showShortcut;
const onLaunch = useCallback(
(target: PinnedTabTarget) => {
const workspaceKey = buildWorkspaceTabPersistenceKey({
serverId: workspace.serverId,
workspaceId: workspace.workspaceId,
});
if (!workspaceKey) return;
navigateToWorkspace(workspace.serverId, workspace.workspaceId);
useLaunchIntentStore.getState().request({ workspaceKey, target });
},
[workspace.serverId, workspace.workspaceId],
);
const launchers = usePinnedLaunchers({ serverId: workspace.serverId, onLaunch });
return (
<>
{isCreating ? (
<Text style={styles.workspaceCreatingText}>{t("sidebar.workspace.status.creating")}</Text>
) : null}
{launchers.length > 0 ? (
<View
style={showPinnedTargets ? undefined : styles.pinnedTargetsHidden}
pointerEvents={showPinnedTargets ? "auto" : "none"}
>
<PinnedTargetsRow launchers={launchers} testIdPrefix="sidebar-pinned-target" />
</View>
) : null}
{shouldRenderActionSlot ? (
<SidebarWorkspaceTrailingActionSlot>
<SidebarWorkspaceTrailingActionBase
@@ -2689,6 +2719,9 @@ const styles = StyleSheet.create((theme) => ({
fontSize: theme.fontSize.xs,
flexShrink: 0,
},
pinnedTargetsHidden: {
opacity: 0,
},
kebabButton: {
padding: 2,
borderRadius: 4,