Files
paseo/packages/app/src/screens/workspace/visible-agent-ids.ts
T
Mohamed Boudraandpaseo-bot[bot] e135e9c101 Open files and subagents in a closable sheet on phones (#6827)
* feat(app): open files and subagents in a closable sheet on compact

On phones, opening a file, a provider subagent, or a Paseo subagent now shows it
in a bottom sheet over the current tab instead of adding a tab, so the tab strip
stays for terminals and root agents. Paseo subagents get "Open as tab" to move
the same tab (same tabId and state) into a full tab with a composer; agents in a
sheet hide the composer.

The decision lives in the workspace layout store's openTab, driven by a
per-kind `compactSheet` rule in the panel manifest, so every open path (chat
links, Explorer, subagents track, notifications, plugins) follows it without
call-site branching, and a new kind opts in with one manifest entry. The app
shell publishes the layout mode (compact / single-pane / split-panes) once, so
callers stop passing isCompact for placement.

A sheet holding unsaved edits is never implicitly displaced; later opens go to
tabs underneath, and closing it goes through the existing discard guard. The
editor host is position: sticky because CodeMirror's scrollIntoView otherwise
scrolls the bottom sheet's container and drags the sheet to full height.

* test(app): fail the compact sheet snap check when the sheet is oversized

---------

Co-authored-by: paseo-bot[bot] <paseo-bot[bot]@users.noreply.github.com>
2026-10-14 00:20:10 +07:00

49 lines
1.6 KiB
TypeScript

import { useRef } from "react";
import { collectAllPanes, type WorkspaceLayout } from "@/stores/workspace-layout-store";
import type { WorkspaceTab } from "@/workspace-tabs/model";
import { deriveWorkspacePaneState } from "./workspace-pane-state";
export function selectVisibleAgentIds(input: {
layout: WorkspaceLayout | null;
tabs: WorkspaceTab[];
/** The workspace sheet's tab, shown over the panes. */
sheetTab?: WorkspaceTab | null;
routeFocused: boolean;
focusedPaneOnly: boolean;
}): string[] {
if (!input.routeFocused || !input.layout) {
return [];
}
const panes = input.focusedPaneOnly
? collectAllPanes(input.layout.root).filter((pane) => pane.id === input.layout?.focusedPaneId)
: collectAllPanes(input.layout.root);
const targets = panes.flatMap((pane) => {
const target = deriveWorkspacePaneState({ pane, tabs: input.tabs }).activeTab?.descriptor
.target;
return target ? [target] : [];
});
if (input.sheetTab) targets.push(input.sheetTab.target);
return [
...new Set(targets.flatMap((target) => (target.kind === "agent" ? [target.agentId] : []))),
].sort();
}
export function useVisibleAgentIds(input: {
layout: WorkspaceLayout | null;
tabs: WorkspaceTab[];
sheetTab: WorkspaceTab | null;
routeFocused: boolean;
focusedPaneOnly: boolean;
}): string[] {
const nextAgentIds = selectVisibleAgentIds(input);
const stableAgentIds = useRef<string[]>([]);
if (
stableAgentIds.current.length !== nextAgentIds.length ||
stableAgentIds.current.some((agentId, index) => agentId !== nextAgentIds[index])
) {
stableAgentIds.current = nextAgentIds;
}
return stableAgentIds.current;
}