# Workspace UI Design Guidelines How settings and workspace views in AgentWorks (Builder automations and Crew projects) are built. New views follow this page; existing views converge to it. See `setup-consolidation.md` for the consolidation that established it. ## Shared header Every workspace view renders one `WorkspaceViewHeader` (`frontend/src/components/workflow/WorkspaceViewHeader.tsx`). Plan and Report are the only views without one (canvas overlays, not titled views). See `header-consolidation.md` for the migration that established this. - Leading icon in an `h-9 w-9` tile (`h-4` glyph), sized to span the title + subtitle block. - Title `text-sm font-semibold`, subtitle `text-xs text-muted-foreground`, both rendered by the header — views never override the sizes. Optional inline `context` (counts, badges) sits next to the title; a full-width `below` row holds stats or pills. - Actions on the right: Ask AI left, refresh right, via `WorkspaceViewActions`. The order is a product rule. Per-tab extras (`tabActions`) render left of the pair; pickers and dropdowns live in the content, never in the header row. - Tabs, when a view has them, belong to the header via its `tabs` prop (`{ value, onChange, options, ariaLabel }`), rendered by the shared `WorkspaceViewTabs` pinned to the header's bottom edge. Tabs may carry icons and counts. Never build a second tab row. - `bare` variant when the shell owns the row (Costs, Execution Logs), `sticky` for scroll views — both visually identical to the standard. ## Ask AI and chat One function sends every right-pane message to chat: `sendWorkspacePaneMessageToChat` (`frontend/src/utils/workspacePaneChat.ts`). It resolves the conversation (automation, tab, or Crew project), opens the left chat, queues behind a running turn, and restores stuck sessions. Every Ask AI button, report widget, human-decision card, and Pulse action funnels through it; new senders need only call it. Product surfaces with their own lane (Crew) route through `AskAIButton`'s `onAsk` prop and still land in the same function. - The header button is `AskAIButton` (icon-only, expands on hover, two-click arm/confirm, "Sent!" acknowledgement), paired with the shared `WorkspaceViewIconButton` refresh. - Ask AI copy is user-visible plain words: no tool names, skill paths, or config keys. Builder messages use the `workspaceAskAI` marker blocks (visible summary + hidden builder instructions), one per view in `WORKSPACE_ASK_AI_MESSAGE`, exhaustive over registered views so a new view cannot ship without one. Views with distinct tabs follow the active tab. Crew messages stay plain strings sent to the project chat. ## Toolbar Toolbars use open/close groups (`WorkspaceToolbarGroup`), never dropdown menus: primary icon buttons stay visible, Ops and Setup collapse into labeled groups that follow the active view. Group buttons are icon-only with tooltips (`aria-label` + `title`), in a fixed order — Setup runs Identity → Integrations → Playbooks. ## Split layout The chat/workspace split has one decision point per surface: Builder uses `resolveWorkspaceLayout` (`frontend/src/components/workflow/workspaceLayoutResolver.ts`), Crew uses `resolveWorkSurfaceLayout` (`frontend/src/products/work/workSurfaceLayoutResolver.ts`). Each is a pure function of its flags (visibility, focus, preview tier, split ratio) returning every pane class, the grid style, and the mount branches — call sites render from it and never branch on the flags themselves. Truth-table unit tests pin every combination, so a new flag or mode cannot silently break an old one. - Panes stay mounted; states toggle visibility and width only. Never swap display modes or layout engines between states. - Visibility restores its own display: a flex pane hidden below md comes back as `hidden md:flex`, never `md:block`. Responsive variants win over base utilities, so `md:block` would override `flex`, collapse `flex-1` scroll regions to content height, and freeze pane scrolling with no error. - Scroll invariant: every `overflow-y-auto` keeps a definite-height ancestor chain in all modes (see Scroll). - Narrow viewports stay single-pane: `focusedPane` picks which pane owns the content row below md; at md+ both panes show as the normal split. ## Cards Tab content is built from the shared `SettingsCard` (`frontend/src/components/ui/SettingsCard.tsx`), matching the shared knowledge-bases design: - Header row: leading icon (`h-4 w-4 text-primary`), `text-sm font-semibold` title, optional muted count pill (`SettingsCount`, e.g. "3 attached"), optional right-side action. - A muted plain-words explainer under the header. - Content below, separated by the card's own spacing. - Empty states use the dashed box (`SettingsEmpty`); loading states use a muted spinner or status line; errors use the destructive banner (`border-destructive/30 bg-destructive/10 text-destructive`). Cards stack with `space-y-4`. Do not nest cards inside cards, and do not repeat the tab title as an in-content heading — the shared header already titles the view. ## Connect tab Setup → Integrations carries a `Connect` tab (tab value `cli`) on Crew projects and Builder automations, both rendering one shared `CliMcpSetupPanel` (`frontend/src/components/integrations/CliMcpSetupPanel.tsx`). The tab points at the installation's API origin on both hosted and local installs. - Lead with AI agent connection over HTTP MCP. Claude Code and Codex get direct registration commands; ChatGPT and Cowork get the same URL or a Cowork plugin. No CLI binary or pasted token is needed. - Browser OAuth handles consent. List connected clients with a Revoke action. - Hosted apps need a public HTTPS URL. A local AI agent may use a configured loopback HTTP URL from the same computer. ## Access and users Users & access is a workspace view (`WorkflowAccessView` via `WorkspaceViewHost`), not a top-menu entry: it follows the shared header and cards rules like any other view, and is gated on multi-user mode with admin/owner checks for management actions. Personal access tokens are managed from the account menu (`AccountControl` → `AccessTokensDialog`), never the top bar. ## Forms Build every form from the kit (`frontend/src/components/ui/`): - `Input`, `Textarea`, `Label`, `Button` (+ variants), `Checkbox`, `Switch`/`ToggleRow`, `SecretField`, `Badge`, `ConfirmationDialog`. - Surfaces and text use theme tokens only: `border-border`, `bg-card`, `bg-muted`, `text-foreground`, `text-muted-foreground`, `text-primary`, `hover:bg-muted`. Status states use the semantic tokens — `warning` (needs attention: running, missed, partly paused), `info` (waiting), `success` (enabled, healthy), `destructive` (errors) — with alpha modifiers for tints (e.g. `border-warning/30 bg-warning/10 text-warning`, paralleling the destructive banner). No hardcoded gray/amber/red/blue/green/purple palettes — the one exception is a documented semantic state color (e.g. the amber bot-enabled toggle, whose meaning is spelled out in the adjacent copy). - Native elements stay only where the kit has no equivalent and the adoption test records the exception: radios and single selects. ## Buttons, badges, counts - Primary `Button` for the page's main save; `outline`/`ghost` `sm` for secondary actions; `ghost` `icon` (`h-7 w-7`) for row actions with `title` tooltips. - Pills are kit `Badge` (`default`/`secondary`/`outline`), not hand-rolled spans — except `SettingsCount` for card-header counts. - Counts read as plain words: "3 attached", "2 saved", "0 attached". ## Delete Destructive removes use `ConfirmationDialog` with `requireText`: the user types the object's name to confirm (GitHub-style). Copy states what is removed and that it cannot be undone. ## Identity icons All identity badges render through `EntityIdentityIcon` (`WorkflowIcon` for automations). An icon value is an emoji/short glyph or an uploaded image stored as a downscaled (~128px) data URL, edited through the shared `IconUploadField` (emoji field + Upload + Remove). Server validation (`agent_go/pkg/workflowtypes/icons.go`) accepts both shapes; prompts and tool results redact image bytes to "(custom uploaded image)". ## Scroll One scroll surface per pane: the tab container owns `overflow-y-auto` and embedded panels join it with `manageOwnScroll={false}`. Never nest a self-scrolling panel inside a scrolling container. Refresh remounts the active tab (a nonce key), since every tab loads on mount. ## Copy Plain business words everywhere the user reads: "apps", "passwords and keys", "folders", "bots". Workflow nouns for automations, project nouns for Crew. No jargon, no config keys, no emoji as icons.