Design system
Docs
Surfaces

Workbench

The side stage: rail, tab strip in the titlebar, files, changes, terminal, and browser panels, and the pane layout around it

HostImplementationNote
Primitive@nyte-ai/ui/tabs, /toolbar, /context-menu, /button
Desktopworkbench/workbench.tsxWorkbench, WorkbenchTabStrip, FilesPanel, ChangesPanel, TerminalPanel, BrowserPanel, WorkspaceFileEditor, WorkspaceSearch
Terminalnone

Workbench

"Eager workbench controller and rail. Open panels stay mounted while hidden; closing a tab releases its panel. The window titlebar owns the tab strip."

PropTypeDescription
targetWorkbenchTargetsession / workspace / home.
paneKeystring"Stable stage identity; views are keyed on it together with their data target."

Hidden views are hidden + aria-hidden + inert. Tab panels are role="tabpanel" with id="{viewKey}-panel-{tab}" and aria-labelledby="{viewKey}-tab-{value}". The rail is <nav aria-label="Workbench navigation">.

Resize sash: role="separator", aria-label="Resize workbench", aria-valuemin/max/now, title="Drag to resize. Double-click to reset." Writes --nyte-active-workbench-width live.

KeyEffect
ArrowLeft+20px
ArrowRight−20px
Home / Endmin / max
Double-clickreset

Tab strip

WorkbenchTabStrip({ viewKey, view, scope, workspacePath }), "the only workbench tab strip, rendered in the window titlebar," on @nyte-ai/ui/tabs with Tabs.List aria-label="Workbench tabs".

InputEffect
Delete on a focused tabClose
Middle-clickClose
Double-click a file tabPin (preview → permanent)
Right-click terminal tabContextMenu: New Terminal, Copy Selection, Clear Terminal, Close Tab
+ buttonMenu "New workbench tab"

Closing a dirty file or a running shell opens a ConfirmDialog. After close, focus returns to the selected tab or to #workbench-toggle. The close button is tabbable only on the active tab and revealed on hover via --_tab-close-opacity.

Files

FilesPanel({ viewKey, workspaceActive }): Pierre FileTree (compact, flattened empty dirs, git status), one WorkspaceFileEditor per open tab, WorkspaceSearch, and a File options Menu with MenuSwitchItems (Line Numbers, Word Wrap, Git Blame, Auto Save, Format on Save; tone="green", size="compact", layout="plain").

KeyEffect
⌘S / Ctrl+SSave the active file
⌘F / Ctrl+FOpen workspace search and focus its input
In the tree's context menu: Escape closes; ArrowDown/Up cycle; Home/End

WorkspaceFileEditor wraps Pierre's EditProvider + CodeView (history 200 entries). Inactive editors stay visibility: hidden so "virtualized editors retain their scroll position." Autosave after 1s idle. Status line is role="status" / role="alert" (Saving…, Saved, Unsaved changes, conflict, error with Retry). "Format/reload are ordinary undoable edits; they do not recreate the editor or its history."

WorkspaceSearch({ onOpen, drafts, active }): 250ms debounce; aria-pressed toggles for match case, whole word, regex; include/exclude globs; results as <section aria-label> per file with <mark> highlights and fully-labelled match buttons.

Changes

See Diff view.

Terminal

TerminalPanel({ owner, sessionId, workspacePath, visible }). Keyboard inside the terminal belongs to the runtime (Ghostty). TerminalStatus renders role="status" / role="alert" lines: "Agent command · read-only", completed / failed / cancelled / interrupted, "Starting terminal…", "Shell exited [with code N]". Buttons: New Terminal, Try Again, Restart.

Browser

BrowserPanel({ surface, visible, historyVisible, url, onUrlChange, toolbarActions }). The address field (inputMode="url", aria-label="Address") selects all on focus and swaps to the raw URL; Enter navigates (or reloads if unchanged); Escape cancels the draft and blurs. The focus ring is drawn on the rounded wrapper via :focus-within: "The ring belongs on the rounded field, not on the square input nested inside it." Bounds stream to the host through a ResizeObserver on every ancestor; surface close is deferred 50ms to survive remounts.

Panes and the stage

ThreadScreen (screens/thread.tsx): "The persistent desktop stage. Pane hosts are keyed only by PaneId; selecting another session changes a host's data binding without replacing its DOM or its view-state owner." Each PaneHost is <section aria-label="[Active ]chat pane">; pointer down or focus makes it active. SplitSash: role="separator", aria-label="Resize chat panes", aria-valuemin=20, aria-valuemax=80.

KeyEffect
Arrows along the split axis±0.02 ratio
Shift + arrow±0.1

"Only a side-by-side split can starve a pane of width, so the pixel floor applies on that axis alone." Sessions are dragged into panes from the sidebar; see Sidebar › Drag to split.

Titlebar

Titlebar (chrome/titlebar.tsx): "Permanent window chrome. The sidebar toggle stays on the rail side; chat actions and the stage-level workbench entry stay at the trailing edge." macOS reserves a 72px traffic-light lane. Handles terminal, new-terminal, and workbench client actions; advertises sidebar, back, and forward via aria-keyshortcuts. See the client action table.