# WorkbenchShell

> The arrangement of a developer tool or an operator's console: an activity bar with one mark per section, an explorer beside it listing the current section's pages, a top bar, full-height panes for the page (pair it with Dock for right and bottom drawers) and a status line along the foot. Same AppChrome and slots as every shell.

<!-- id: ui-shell-workbench · area: ui · stability: stable · html: https://osysharp.com/reference/ui/shell-workbench/ -->

## Summary        {#summary}

`WorkbenchShell` splits navigation in two. The **activity bar** carries one mark per top-level section and is always
in view; the **explorer** beside it lists the pages of the section you are in, with whatever the app adds under them
(saved views, a queue's filters). Pressing the current section's mark — or Mod+B — folds the explorer away, because
once you know where you are the width is worth more than the map. The page fills full-height panes, and a status line
runs along the foot.

```osy title="an operations console" test app=ui-shell-workbench
[Layout]
[AllowAnonymous]
component Console() {
  action Search() { }
  render {
    WorkbenchShell(new AppChrome {
      Product = "Beacon",
      Tagline = "Operations",
      Home = "/",
      Nav = [
        new NavItem { Label = "Queues", Icon = Icons.Inbox, Children = [
          new NavItem { Label = "Open", To = "/", Icon = Icons.CircleDot, Exact = true },
          new NavItem { Label = "Waiting", To = "/waiting", Icon = Icons.Clock },
        ] },
        new NavItem { Label = "Deploys", To = "/deploys", Icon = Icons.Upload },
      ],
    }) {
      Outlet(retain: 8);
      slot search { ShellSearch("Search", onPress: Search); }
      slot explorer { SectionLabel("Saved views"); }
      slot status { Lamp(lit: true, live: true, tone: Tone.Success); Text("Live"); }
    }
  }
}

[Route("/")] [Layout(Console)] [Title("Open")] [Render(CSR)] [AllowAnonymous]
component OpenQueue() { render { Stack(p: 4) { Text("Nothing open."); } } }

[Route("/waiting")] [Layout(Console)] [Title("Waiting")] [Render(CSR)] [AllowAnonymous]
component WaitingQueue() { render { Stack(p: 4) { Text("Nothing waiting."); } } }

[Route("/deploys")] [Layout(Console)] [Title("Deploys")] [Render(CSR)] [AllowAnonymous]
component Deploys() { render { Stack(p: 4) { Text("No deploys today."); } } }
```

## Signature      {#signature}

```osy syntax
WorkbenchShell(AppChrome chrome) {
  Outlet(retain: 8);
  slot explorer { … }   // under the section's pages
  slot search   { … }   // the top bar
  slot actions  { … }   // the top bar, right of search
  slot status   { … }   // the status line along the foot
}
```

## Description    {#description}

### Which items become marks, and which pages does the explorer list?   {#marks}

Every top-level `NavItem` is a mark; a `NavKind.Section`'s children are lifted to the top level (a section heading is
a word, and an activity bar has no room for words). A mark with `Children` is a SECTION: its children are what the
explorer lists while you are in it. A mark with no children is a page on its own, and the explorer lists just it.
A mark's name shows beside it on hover and keyboard focus, with its `Badge`. A mark with a `Badge` carries it as a
dot in the badge's tone, and the badge's words are in the mark's accessible name — `"Inbox — 3"` — so a screen
reader and a test (`Ui.Click("Inbox — 3", within: "Main navigation")`) read the count without pointing at it.

### What happens when a row of the explorer opens a page outside the nav?   {#records}

The explorer stays. A tree in the `explorer` slot — records, files, a backlog — opens pages that are in no section
of the nav (`/orders/42`), and the explorer is not taken away the moment one of its rows is opened: it stays on the
section of the latest open route that is in one, and that section's mark stays lit, as an editor's side bar stays
on the view a file was opened from. This needs the outlet to keep routes open (`Outlet(retain: 8)`); with a single
open route, a page outside every section shows no explorer.

### Where do the drawers come from?   {#drawers}

From the page, through [Dock](https://osysharp.com/reference/ui/dock/) — the inspector a row opens on the right, a log along the bottom. They belong to
the page because they open over what the PAGE has selected; the shell gives them a frame that is full height with
nothing scrolling but the panes.

### Where does `ShowVersion` show the versions?   {#version}

At the far end of the status line. The workbench's brand is a bare mark with no tagline under it, so where every
other shell puts the app's version under the product's name, this one puts the same two lines in its foot, after
whatever the `status` slot holds: `"Tagline · App.Version"` (just the version when there is no tagline) and
`"platform " + App.PlatformVersion`. A test reads them there: `Assert.Visible("platform " + App.PlatformVersion,
within: "Status")`. See [App.Version — which version of your app is running, on which platform release](https://osysharp.com/reference/stdlib/app-version/).

### What happens on a phone?   {#narrow}

Below `Tablet` the activity bar and the explorer become one drawer behind the menu button — every section with its
pages under it, then the `explorer` slot — and the status line stays at the foot.

### How do I drive it in a test?   {#test}

The marks are links in the region "Main navigation"; the explorer is a region named by the section's label; the
status line is "Status". `Ui.Click("Hide the explorer")` folds the explorer, and `Assert.Visible("Live", within:
"Status")` reads the foot.

## See also       {#see-also}

- [Dock](https://osysharp.com/reference/ui/dock/) — the page's right and bottom drawers
- [App shells](https://osysharp.com/reference/ui/shell/) — the `AppChrome` contract every arrangement shares
- [Arrangements — five ways to build a whole app, and which look wears each](https://osysharp.com/reference/ui/arrangements/) — the workbench beside the other four archetypes
