mirror of
https://github.com/getpaseo/paseo.git
synced 2026-11-01 06:02:56 +00:00
Put sheets on one stack and show an agent's facts in its sheet (#6958)
* 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>
This commit is contained in:
@@ -212,7 +212,7 @@ parentAgentId === thisAgent.id AND !archivedAt
|
||||
|
||||
- **Provider subagents** are child executions owned by Claude, Codex, or OpenCode. They are not inserted into `AgentManager` as managed agents. Providers emit a separate descriptor and timeline stream through `agent.provider_subagents.*`; the client keeps that state outside the normal agent store and merges only the presentation rows into the track. A descriptor's optional `parentSubagentId` identifies its direct provider-subagent parent; an absent value identifies a direct child of the managed agent.
|
||||
|
||||
Clicking either kind opens a workspace tab. On compact layouts it opens in the [workspace sheet](explorer-sidebar.md#workspace-sheet) instead; the sheet hides the composer, and a Paseo subagent's sheet offers **Open as tab** to get it back. A Paseo subagent tab is a normal interactive agent pane. A provider subagent tab is a read-only timeline pane with no composer, archive, detach, rewind, or fork actions. It shows its own direct children in a subagents track. Both panes use `AgentStreamView`, so message, reasoning, tool-call, and layout rendering stay identical.
|
||||
Clicking either kind opens a workspace tab. On compact layouts it opens in a [sheet](explorer-sidebar.md#sheets) over the subagents list instead; the sheet hides the composer and shows the agent's model and thinking as one line in its place (`PaneSubtitle`, the row provider subagents already use), and a Paseo subagent's sheet offers **Open as tab** (the full-screen glyph in the header) to get the composer back. A Paseo subagent tab is a normal interactive agent pane. A provider subagent tab is a read-only timeline pane with no composer, archive, detach, rewind, or fork actions. It shows its own direct children in a subagents track. Both panes use `AgentStreamView`, so message, reasoning, tool-call, and layout rendering stay identical.
|
||||
|
||||
Provider timelines use the same structural timeline item format but deliberately have a separate lifecycle and transport. A provider thread/session identifier is not a Paseo agent identifier, and closing its tab is always layout-only.
|
||||
|
||||
|
||||
+21
-14
@@ -7,7 +7,7 @@ contracts.
|
||||
| ---------------- | ---------------------------- | ------------------------------------------ |
|
||||
| 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 |
|
||||
| Workspace sheet | Compact look-ups | Stacks tabs; each closes or becomes a tab |
|
||||
| Sheets | Compact look-ups | One tab each, on the app-wide sheet stack |
|
||||
|
||||
## Panel host contract
|
||||
|
||||
@@ -23,23 +23,30 @@ 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.
|
||||
|
||||
## Workspace sheet
|
||||
## Sheets
|
||||
|
||||
On compact layouts, files and subagents open in a closable sheet over the workspace, so the tab
|
||||
switcher keeps only agents and terminals. A panel kind opts in with `compactSheet` in its manifest
|
||||
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 sheet holds one ordinary
|
||||
`WorkspaceTab`, and sheets stack: a target opened from a sheet covers it, and closing the top sheet
|
||||
(drag, Close, backdrop, or Android Back) uncovers the one below. Revealing a target that already has
|
||||
a sheet brings that sheet back to the top and closes the sheets above it, except ones holding unsaved
|
||||
work. Opening a sheet as a tab dismisses the sheets above it, since the
|
||||
tab would sit under them. The stack is not persisted.
|
||||
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. Sheets render in
|
||||
the workspace tree rather than through `IsolatedBottomSheetModal`: their panels need the navigator
|
||||
and every workspace provider an ordinary pane has, and a portal drops them
|
||||
([floating-panels.md Gotcha 7](floating-panels.md#gotcha-7--a-sheet-cannot-read-context-from-its-call-site)).
|
||||
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
|
||||
|
||||
+18
-8
@@ -343,11 +343,20 @@ Do not treat `onChange(-1)` as a close by itself. In a stacked
|
||||
another pushed sheet. Close React state from `onDismiss`; use `onChange` only to
|
||||
track phase.
|
||||
|
||||
All sheets use the app-wide Gorhom modal provider through `IsolatedBottomSheetModal`. This
|
||||
includes native centered dialogs: separate React Native Modals cannot present a root-owned
|
||||
sibling dialog over an existing iOS presenter. The one exception is the
|
||||
[workspace sheet](explorer-sidebar.md#workspace-sheet), an in-tree Gorhom `BottomSheet` that hosts a
|
||||
whole workspace panel.
|
||||
All sheets use the app-wide Gorhom modal provider through `IsolatedBottomSheetModal`, with no
|
||||
exceptions: one stack means a sheet opened from any sheet lands on top of it and uncovers it on
|
||||
close. This includes native centered dialogs: separate React Native Modals cannot present a
|
||||
root-owned sibling dialog over an existing iOS presenter. A sheet that must draw inside a subtree
|
||||
(the [sheet tabs](explorer-sidebar.md#sheets) draw inside the compact workspace column so their
|
||||
panels read the workspace providers) presents from inside a `SheetHostScope`, which moves the
|
||||
portal host and container measurement while the sheet still joins the same stack. That export is
|
||||
what `patches/@gorhom+bottom-sheet+*.patch` adds.
|
||||
|
||||
A sheet whose owner may veto the close (unsaved work) passes `enableDismissOnClose={false}`. Every
|
||||
close path then parks the sheet at index -1 with its content mounted; the owner hears
|
||||
`onChange(-1)`, runs its guard, and either snaps back to 0 or drops its state, which unmounts the
|
||||
sheet and takes it off the stack. Calling `dismiss()` on such a sheet bypasses the guard and
|
||||
unmounts it, so its backdrop and Back press call `close()`.
|
||||
|
||||
The wrapper must give Gorhom an object ref. Gorhom accepts a callback ref at the React boundary
|
||||
but stores it in the modal queue and later reads `.current`; provider dismissal, replacement,
|
||||
@@ -357,9 +366,10 @@ forwards commands to the current Gorhom handle.
|
||||
Request a backdrop with `backdropOpacity`; do not install a caller-owned backdrop. On web,
|
||||
Gorhom's default backdrop closes its own sheet, even when another modal is above it. Its animated
|
||||
pointer-event changes can expose a lower backdrop during a transition. The shared web backdrop
|
||||
keeps its press target until portal teardown and asks the provider to dismiss the top modal.
|
||||
Gorhom's presentation queue remains the authority; backdrop mount order is irrelevant. Native
|
||||
retains Gorhom's gesture backdrop and the same push/replace presentation semantics.
|
||||
keeps its press target until portal teardown and asks the provider to dismiss the top modal
|
||||
(a guarded sheet closes itself instead, see above). Gorhom's presentation queue remains the
|
||||
authority; backdrop mount order is irrelevant. Native retains Gorhom's gesture backdrop and the
|
||||
same push/replace presentation semantics.
|
||||
|
||||
## Gotcha 7 — A sheet cannot read context from its call site
|
||||
|
||||
|
||||
+2
-1
@@ -21,7 +21,8 @@ Authoritative terminology. UI label wins. Don't invent synonyms; use what's here
|
||||
- **Repository / Remote** — Internal Git observations. They may produce mutable kind, branch, and project-grouping metadata but never the host-local project ID, root, display name, or workspace membership. No UI label.
|
||||
- **Sidebar items** — The configurable entries of the app sidebar, in two sections. Header: the rows at the head of the sidebar — New workspace, History, Search, Schedules, and plugin header items. Footer: the full-width rows between Add project and the footer icon row — Usage and plugin footer items. Add project and the icon row (Hosts, Help and support, Settings) are fixed and are not sidebar items. A plugin item may render several rows, such as one per bot; it is still one item that moves and hides as a block. Each section's order and visibility is one per-device preference (`AppSettings.sidebarNavItems`, `AppSettings.sidebarFooterItems`); every item may be off. UI: the Header and Footer cards on the Sidebar settings page. Code: `packages/app/src/sidebar-nav/model.ts` resolves a section, and the sidebar (`components/sidebar/sidebar-nav-rows.tsx`, the footer in `components/left-sidebar.tsx`) and the settings page (`screens/settings/sidebar/sidebar-nav-section.tsx`) read it through `useSidebarNavItems(section)`. Never hardcode one of these entries or add a second place that decides which ones show. Don't confuse with **Explorer sidebar**.
|
||||
- **Explorer sidebar** — The dedicated navigation dock containing the Files and Changes trees. UI: "Explorer sidebar". Code: `packages/app/src/workspace-tabs/explorer-sidebar.ts`; persisted pane identifiers retain the `explorer` spelling. See [explorer-sidebar.md](explorer-sidebar.md).
|
||||
- **Workspace sheet** — A compact sheet that holds one file or subagent tab over the workspace without adding it to the tab switcher; opening another target from a sheet stacks a new one on top. UI action: "Open as tab". Code: `sheetsByWorkspace` in `packages/app/src/stores/workspace-layout-store.ts`, `WorkspaceSheetHost` (`packages/app/src/screens/workspace/workspace-sheet-host.tsx`). See [explorer-sidebar.md](explorer-sidebar.md#workspace-sheet).
|
||||
- **Sheet** — Any bottom sheet; there is one kind and one app-wide stack, so a sheet opened from a sheet lands on top of it and closing it uncovers the one below. Code: `IsolatedBottomSheetModal` (`packages/app/src/components/ui/isolated-bottom-sheet-modal`). Forbidden: "Workspace sheet", "modal sheet" as a distinct kind. See [floating-panels.md](floating-panels.md).
|
||||
- **Sheet tab** — A compact-layout sheet holding one file or subagent tab over the workspace without adding it to the tab switcher. UI action: "Open as tab". Code: `SheetTab` and `sheetsByWorkspace` in `packages/app/src/stores/workspace-layout-store.ts`, `SheetTabs` (`packages/app/src/screens/workspace/sheet-tabs.tsx`). See [explorer-sidebar.md](explorer-sidebar.md#sheets).
|
||||
- **Side pane** — An ordinary right-side workspace pane used for content opened beside the user's work. It closes and participates in workspace splits like every other main-host pane. See [explorer-sidebar.md](explorer-sidebar.md#side-pane). Forbidden: "Side panel".
|
||||
- **Directory-backed surface** — A right-sidebar surface whose content is determined by the workspace's `cwd`, so two workspaces on the same directory see identical content: git diff/status, forge change-request info, file preview/explorer contents. Keyed by `(serverId, cwd)`, never `workspaceId`. See [architecture.md](architecture.md#right-sidebar-boundary-directory-backed-vs-workspace-owned).
|
||||
- **Workspace-owned state** — Per-workspace state that never leaks to a same-`cwd` sibling: tabs, agents, terminals, panes, title, plus review drafts, diff-mode overrides, composer attachments, and file-explorer open/expand state. Keyed by `workspaceId` (`cwd` only as a fallback for old payloads). See [architecture.md](architecture.md#right-sidebar-boundary-directory-backed-vs-workspace-owned).
|
||||
|
||||
@@ -52,6 +52,11 @@ is another, with its own icons and hover-revealed actions — but both hand the
|
||||
and take `closeOnSelect` to say whether choosing them ends the surface. A row that owns its press
|
||||
outright leaves the surface open behind whatever it opened, and skips the iOS wait.
|
||||
|
||||
A row whose result opens a sheet passes `closeOnSelect={false}`: the sheet stacks over the menu
|
||||
sheet and uncovers it on close, so the menu is where the user lands next. A row that opens a tab
|
||||
closes, because the tab replaces the pane the menu belongs to. The subagents track asks the layout
|
||||
store which one a row is (`useSubagentOpensInSheet`), since the answer changes with the layout.
|
||||
|
||||
## Pages
|
||||
|
||||
A submenu is a page, declared as data on the surface and reached by a `MenuSubTrigger` whose `id`
|
||||
@@ -193,6 +198,9 @@ its own.
|
||||
backdrop, so there is exactly one `Modal` on native no matter how deep the menu goes.
|
||||
- **Sheet stacking and backdrop dismissal belong to `IsolatedBottomSheetModal`.** Menus only
|
||||
request a backdrop opacity. See [floating-panels.md](floating-panels.md#gotcha-6--bottom-sheet-refs-are-not-lifecycle-truth).
|
||||
- **A menu closes when its panel leaves the screen.** `MenuSurface` closes the menu when its
|
||||
retained panel goes inactive. Hiding the surface alone leaves the open state behind, and the
|
||||
menu would reappear with the panel on the next tab switch.
|
||||
- **Retained panels own visibility.** The shared menu surface unmounts when its panel becomes
|
||||
inactive. An async action can navigate before its menu closes; a hidden panel must not leave a
|
||||
portal backdrop blocking the destination. On web the chat suspends one commit after it goes
|
||||
|
||||
Reference in New Issue
Block a user