CI / changes (push) Successful in 6m6s
Docker / setup (push) Successful in 14s
Docker / publish (push) Skipped
Docker / build (push) Failing after 10m1s
CI / relay-tests (push) Canceled after 0s
CI / lint (push) Canceled after 0s
CI / typecheck (push) Canceled after 0s
CI / server-tests (ubuntu-latest) (push) Canceled after 0s
CI / server-tests (windows-latest) (push) Canceled after 0s
CI / desktop-tests (ubuntu-latest) (push) Canceled after 0s
CI / desktop-tests (windows-latest) (push) Canceled after 0s
CI / app-tests (push) Canceled after 0s
CI / sdk-tests (push) Canceled after 0s
CI / playwright (shard 1/4) (push) Canceled after 0s
CI / playwright (shard 2/4) (push) Canceled after 0s
CI / playwright (shard 3/4) (push) Canceled after 0s
CI / playwright (shard 4/4) (push) Canceled after 0s
CI / format (push) Canceled after 0s
CI / cli-tests (shard 1/3) (push) Canceled after 0s
CI / cli-tests (shard 2/3) (push) Canceled after 0s
CI / cli-tests (shard 3/3) (push) Canceled after 0s
* fix(app): keep file navigation in place Retargeting a File tab allocated a new tab identity, causing the tab track to unmount and remount during navigation. Preserve the identity for File-to-File changes while retaining new instances for cross-kind replacements. * feat(app): expand workspace panel actions Keep the Command Center aligned with workspace tab and pane primitives. Add explicit panel placement, focused open behavior, keyboard navigation for empty panes, and a Changes pill that toggles the visible Side panel without closing its tab.
99 lines
5.0 KiB
Markdown
99 lines
5.0 KiB
Markdown
# Side panel
|
|
|
|
The companion surface beside the user's work: Changes, Files, the pull request, and
|
|
anything an agent asks to show alongside its output.
|
|
|
|
Everything that opens, reveals, hides, or asks about it goes through
|
|
`packages/app/src/workspace-tabs/side-panel.ts`. Nothing else branches on which
|
|
surface the current layout uses.
|
|
|
|
## Three renderings, two facts
|
|
|
|
| Layout | Rendering |
|
|
| ---------------------- | ----------------------------------------------- |
|
|
| compact | slide-over overlay, owned by the panel store |
|
|
| non-compact + splits | dedicated right pane, owned by the layout store |
|
|
| non-compact, no splits | ordinary tabs in the focused pane |
|
|
|
|
The third case is the one that bites. A native tablet renders one pane at a time and
|
|
the tab row only lists the focused pane's tabs, so anything routed into a separate
|
|
side panel pane there disappears with no way back.
|
|
|
|
## Placement
|
|
|
|
Where a tab lands is an intent the caller declares, not something the store infers
|
|
from a `paneId` that happens to be in scope. `WorkspaceTabPlacement`
|
|
(`packages/app/src/stores/workspace-layout-actions.ts`) has four modes, and the
|
|
difference between them only shows up when the tab is already open somewhere:
|
|
|
|
| Mode | New tab | Tab already open |
|
|
| --------- | ----------------- | ---------------------------- |
|
|
| `pane` | opens in the pane | **moves** to the pane |
|
|
| `prefer` | opens in the pane | stays where the user left it |
|
|
| `focused` | focused pane | focuses it in place |
|
|
| `ambient` | focused pane | focuses it in place |
|
|
|
|
`pane` is a pane-local affordance — that pane's `+` menu, its empty-state launcher.
|
|
The user is placing the tab and expects it to arrive.
|
|
|
|
`prefer` is an implicit supporting open — an agent's file link or a failed workspace
|
|
setup. It has an opinion about new tabs only. Yanking a tab out of the pane the user
|
|
deliberately moved it to is the failure this mode exists to prevent.
|
|
|
|
`ambient` differs from `focused` in one rule: reconciliation opens tabs with nobody
|
|
behind the click, and the side panel can hold focus from an earlier reveal, so an
|
|
ambient open of something the user works _in_ (an agent, a terminal, a browser) is
|
|
pushed out of the side panel. A `focused` open honours the focused pane whatever it
|
|
is, because the user is standing in it.
|
|
|
|
Deduplication lives in `openTabInLayoutFocused`, so it obeys the placement intent for
|
|
every caller. Do not re-place a tab after opening it.
|
|
|
|
## Lifecycle
|
|
|
|
The side panel lives in the ordinary split tree, so it is a pane like any other until
|
|
it empties or the user dismisses it:
|
|
|
|
- **Close pane** on the side panel **hides** it; its tabs are waiting on the next reveal.
|
|
- **Close pane** on any other pane **removes** it.
|
|
- Closing the Side panel's final tab hides the pane. Revealing it returns the empty launcher.
|
|
This is close-only: claiming or dragging that tab to another pane leaves the empty Side panel
|
|
visible.
|
|
- Nothing takes the last visible pane away. `closePane` will not remove it, and
|
|
neither `closePane` nor `hideSidePanel` will hide it — including the header toggle
|
|
reaching `hideSidePanel` after every other pane is gone. The gate is one check in
|
|
`setPaneHiddenInLayout`; do not add a second one upstream.
|
|
|
|
`closePane` in the layout store owns that distinction. React renders the button and
|
|
does not decide what it means.
|
|
|
|
Dragging a pane's last tab into another pane collapses the source pane — except the
|
|
side panel, which the user summons and expects to find again, split size and all.
|
|
|
|
## What the side panel never does
|
|
|
|
It does not seed itself. Revealing it opens an empty pane with the launcher; the user
|
|
picks what goes in. Detecting a pull request makes the launcher's card available and
|
|
opens nothing. A failed workspace setup adds a background tab to the main pane.
|
|
Running and successful setup never seed tabs.
|
|
|
|
## Routing preference
|
|
|
|
**Settings → General → Open supporting tabs in the Side panel**, on by default,
|
|
persisted in client app settings as `openSupportingTabsInSidePanel`. Off means new
|
|
supporting tabs land in the focused pane instead. Mobile ignores it. Explicit actions
|
|
— the header toggle, a pane's `+` menu, the empty-state launcher — are never subject
|
|
to it. The Changes keyboard shortcut and the diff-stat pill above an agent composer are
|
|
supporting opens: they follow the preference and focus an existing Changes tab wherever
|
|
the user left it. When Changes is already visible in the Side panel, the diff-stat pill
|
|
hides the panel without closing the tab. A different visible Side panel tab switches to
|
|
Changes instead.
|
|
|
|
## The name
|
|
|
|
The product surface is the **Side panel**. **File Explorer** is still the name of the
|
|
Files view inside it. Persisted identifiers keep their pre-rename spelling: the pane
|
|
id is the literal string `"explorer"` (`SIDE_PANEL_PANE_ID`) and the persisted layout
|
|
key is `explorerPaneIdByWorkspace`. The persisted state schema is strict, so renaming
|
|
either would fail every saved blob and wipe the layout it describes.
|