mirror of
https://github.com/getpaseo/paseo.git
synced 2026-10-23 09:42:53 +00:00
* Put sheet tabs on the one sheet stack Sheets are one primitive with one stack. A sheet opened from any sheet stacks on top of it, and closing it uncovers the sheet underneath. The compact workspace's file and subagent sheets were an in-tree Gorhom BottomSheet with their own stacking, so a subagent chosen from the subagents list sheet replaced the list instead of covering it. - SheetHostScope: a subtree hosts the sheets presented inside it, so the sheet tabs draw inside the compact column and read the workspace providers while still joining the app-wide stack. Needs Gorhom's BottomSheetModalInternalProvider, which the patch exports. - IsolatedBottomSheetModal takes enableDismissOnClose; false keeps the content mounted through a close so the unsaved-changes guard can veto it. Its backdrop then closes the sheet rather than dismissing the top modal. The Gorhom patch also keeps a dismissing modal's status through its final onChange(-1), without which such a modal unmounting while presented never left the portal. - SheetTabs (was WorkspaceSheetHost) renders one IsolatedBottomSheetModal per store entry. The store never reorders sheets; a reveal of a covered sheet drops the unmodified sheets above it. - Subagent rows that open a sheet keep the list open (closeOnSelect false); rows that open a tab still close it. - A menu closes when its retained panel goes inactive, instead of hiding and reappearing with the panel on the next tab switch. Co-Authored-By: Claude <noreply@anthropic.com> * Show an agent's model and thinking in its sheet, and fit Open as tab in the header rail A Paseo agent shown in a sheet has no composer, so the facts the composer's model and thinking controls would show now sit in one line under the sheet header, the same row provider subagents already get from the daemon. The line resolves exactly as the controls do (resolveAgentModelSelection), from the session agent and the providers snapshot, and shows wherever the pane host is a sheet. Open as tab becomes the full-screen glyph beside Close, drawn by the shared SheetHeaderIconButton, so the title keeps its width. The tab switcher sheet is titled "Workspace tabs" so it reads as what it lists. Co-Authored-By: Claude <noreply@anthropic.com> * Show mode and context usage in the sheet facts line The line under a sheet's header now carries everything the composer's controls and context meter show: model, thinking, mode, and used/max context tokens with the share, each resolved as its control resolves it. Co-Authored-By: Claude <noreply@anthropic.com> * Re-render subagent rows when the workspace's tabs change A row decides at render time whether choosing it opens a sheet or reveals a tab, and keeps the list open only for a sheet. The hook behind it only subscribed to the layout mode, so a row rendered while its agent was a full tab kept closing the list after that tab was gone: on iOS, promoting a managed child, closing its tab, and reopening it from the list dropped the list under the sheet. Subscribe to the workspace's layout too. Co-Authored-By: Claude <noreply@anthropic.com> --------- Co-authored-by: Claude <noreply@anthropic.com>
131 lines
8.5 KiB
Markdown
131 lines
8.5 KiB
Markdown
# Explorer sidebar and side pane
|
||
|
||
The Explorer sidebar and the side pane share panel implementations, but they have different shell
|
||
contracts.
|
||
|
||
| Surface | Purpose | Lifecycle |
|
||
| ---------------- | ---------------------------- | ------------------------------------------ |
|
||
| Explorer sidebar | Files and Changes navigation | Cmd+E shows or hides the dedicated dock |
|
||
| Side pane | Ordinary workspace content | Created and closed like any workspace pane |
|
||
| Sheets | Compact look-ups | One tab each, on the app-wide sheet stack |
|
||
|
||
## Panel host contract
|
||
|
||
Every desktop panel registers its supported `PaneHost` values and presentation. Launchers derive
|
||
fixed-target labels and icons from that registration, filter by host, and never substitute one
|
||
panel type for another. Tab moves reject unsupported destinations, and placement resolves only to
|
||
a compatible pane.
|
||
|
||
Files and Changes are the Explorer defaults. Their panel manifests mark them as singletons,
|
||
so a pane’s + menu omits each while that pane already contains it. Closing one makes its menu
|
||
item available again. Other compatible tabs, including agents, terminals, files, and diffs,
|
||
can move between Explorer and main panes.
|
||
Keep panel implementations independent of either shell. `WorkspacePanelHost` owns mounting and
|
||
retention, while each shell owns its tabs, focus, dragging, resizing, and shortcuts.
|
||
|
||
## Sheets
|
||
|
||
On compact layouts, files and subagents open in a sheet over the workspace, so the tab switcher
|
||
keeps only agents and terminals. A panel kind opts in with `compactSheet` in its manifest
|
||
(`packages/app/src/panels/panel-manifest.ts`), and the layout store's `openTab` decides between
|
||
sheet and tab, so callers open targets the same way on every layout. Each such sheet holds one
|
||
ordinary `WorkspaceTab` (`SheetTab` in the store) and is an `IsolatedBottomSheetModal` like every
|
||
other sheet in the app, so it sits on the one app-wide stack: a sheet opened from any sheet, a
|
||
subagent chosen from the subagents list included, lands on top of it, and closing the top sheet
|
||
(drag, Close, backdrop, or Android Back) uncovers the one below. The list stays open under the
|
||
sheet it opened; see [menus.md](menus.md#selecting-an-item-on-ios) for the row contract.
|
||
|
||
The store keeps `sheetsByWorkspace` in opening order and never reorders it, because moving a sheet
|
||
remounts its panel. Revealing a target that already has a sheet uncovers that sheet by closing the
|
||
ones above it, except ones holding unsaved work, which stay where they are. Opening a sheet as a tab
|
||
dismisses the sheets above it, since the tab would sit under them. The stack is not persisted.
|
||
|
||
Sheets are modal and cover the whole compact column, header included. A sheet's header is its drag
|
||
handle, because the panel scrolls on its own and content panning would fight it. Its trailing rail
|
||
holds icon buttons only (`SheetHeaderIconButton`): Open as tab is the full-screen glyph beside
|
||
Close, so the title keeps its width. The sheets present
|
||
from inside the compact column's `SheetHostScope`, so their panels read the navigator and every
|
||
workspace provider an ordinary pane has without a context bridge
|
||
([floating-panels.md Gotcha 6](floating-panels.md#gotcha-6--bottom-sheet-refs-are-not-lifecycle-truth)).
|
||
|
||
A sheet holding unsaved work is never implicitly displaced; only its guarded close removes it.
|
||
Moving its tab to another host remounts the panel, and the file editor keeps unsaved text in the
|
||
mounted view, so the move would drop it. Widening the layout is the known exception: it still moves
|
||
the sheet's tab into a pane, and edits the autosave has not started writing are lost.
|
||
|
||
## Explorer sidebar
|
||
|
||
`packages/app/src/workspace-tabs/explorer-sidebar.ts` owns show, hide, toggle, and view selection.
|
||
On desktop, the shell is rendered outside the workspace split canvas so it divides the full
|
||
workspace, including the header. It has its own persisted width and resize handle. Main-pane splits
|
||
never read or modify that width.
|
||
|
||
`packages/app/src/workspace-tabs/open-supporting-view.ts` owns semantic Changes and pull-request
|
||
opens. Compact and wide native layouts select the matching Explorer tab. Desktop Changes opens
|
||
follow the shared diff preference. Desktop pull requests use their Main panel, On the side, or
|
||
Explorer sidebar setting. Automatic PR discovery follows that preference once per workspace without
|
||
interrupting the user's work. Closing the tab opts that workspace out of future automatic opens,
|
||
even for a different PR; moving or reordering it remains the user's choice.
|
||
Callers request the content and never choose the shell.
|
||
The composer Changes pill is a two-stage desktop action: it first reveals Explorer on Changes, then
|
||
routes later presses to the working diff through the shared diff preference.
|
||
|
||
The persisted layout still contains the Explorer pane so tabs survive reloads. The renderer removes
|
||
that pane from the workspace split tree and docks it separately. Persisted identifiers retain the
|
||
literal `"explorer"` pane id and `explorerPaneIdByWorkspace` key for compatibility.
|
||
|
||
Explorer uses the shared workspace tab row and ordinary tab context menus. Files and Changes
|
||
hide their close buttons through the panel manifest; close them from the tab context menu.
|
||
Other tabs reveal the close control on hover. The + menu opens compatible panels in the dock and
|
||
omits Agent and terminal profiles. Agents and terminals can still be dragged into Explorer.
|
||
Bulk-close actions apply only to the dock's tabs. Explorer tabs can be reordered and dragged
|
||
between compatible panes, but the dock cannot be split or maximized. Selecting an Explorer tab
|
||
does not change workspace focus.
|
||
|
||
Cmd+E shows or hides Explorer without changing its selected view. Compact layouts use the combined
|
||
full-screen Explorer overlay for Changes, Files, and pull requests, and close it after a file opens. Compact Changes has no tree rail; its overview is the Jump to file action (`packages/app/src/git/jump-to-file/`), a sheet over the same changed-files tree the desktop rail renders.
|
||
Wide native layouts without pane splits use the same combined content in a resizable inline dock;
|
||
opening a file leaves that dock visible. Both presentations keep their selection in the panel store
|
||
and reuse the layout store's per-workspace Explorer width. They do not create a second Explorer
|
||
lifecycle.
|
||
|
||
## Side pane
|
||
|
||
`packages/app/src/workspace-tabs/open-beside.ts` owns content opened beside the user's work. The
|
||
layout store remembers one ordinary pane per workspace. The first side open creates a full-height
|
||
right split around the workspace root; later side opens reuse it. Only the `split-panes` layout
|
||
mode opens beside. Compact and single-pane layouts (touch tablets) open the same target in the main
|
||
pane, so callers never check whether splits render.
|
||
|
||
Removing a pane clears its remembered id; a later side open creates a new pane. The last visible
|
||
ordinary pane stays when its final tab closes and shows the New launcher. An empty workspace does
|
||
not automatically create an agent draft tab; choosing Agent opens one. Explorer cannot replace the
|
||
workspace canvas, even when visible. Restoring a saved layout enforces the same rule while preserving Explorer and saved
|
||
tab content. There is no hidden side-pane lifecycle.
|
||
|
||
Placement intent still controls existing tabs:
|
||
|
||
| Mode | New target | Existing target |
|
||
| --------- | --------------------------- | --------------------------------- |
|
||
| `pane` | opens in the requested pane | moves to the requested pane |
|
||
| `prefer` | opens in the requested pane | stays where the user placed it |
|
||
| `focused` | opens in the focused pane | focuses it where it already lives |
|
||
| `ambient` | opens in a compatible pane | focuses it where it already lives |
|
||
|
||
Explicit **Open to Side** uses `pane`. Implicit opens use `prefer`, so a preference affects only a
|
||
new target and never yanks an existing tab out of a user-selected pane.
|
||
|
||
## Routing preferences
|
||
|
||
Desktop **Settings → Layout → Open location** has independent Main panel or On the side choices for
|
||
Explorer Files, diffs, chat files, files opened from diffs, and subagents. They default to Main
|
||
panel. Mobile ignores them.
|
||
|
||
Pull requests have a three-way open location: Main panel, On the side, or Explorer sidebar. Explorer
|
||
sidebar is the default. Compact layouts always open pull requests in Explorer regardless of this
|
||
desktop preference.
|
||
|
||
Panels request an implicit open through the narrow `openPreferredTarget(target, source)` pane
|
||
contract. Entry points outside panels use `openPreferredWorkspaceTarget`. Do not branch on a
|
||
specific shell inside a panel.
|