mirror of
https://github.com/getpaseo/paseo.git
synced 2026-10-14 13:22:37 +00:00
* 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>
49 lines
1.6 KiB
TypeScript
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;
|
|
}
|