ui design guidelines - manishiitg/agentworks GitHub Wiki
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-9tile (h-4glyph), sized to span the title + subtitle block. - Title
text-sm font-semibold, subtitletext-xs text-muted-foreground, both rendered by the header — views never override the sizes. Optional inlinecontext(counts, badges) sits next to the title; a full-widthbelowrow 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
tabsprop ({ value, onChange, options, ariaLabel }), rendered by the sharedWorkspaceViewTabspinned to the header's bottom edge. Tabs may carry icons and counts. Never build a second tab row. barevariant when the shell owns the row (Costs, Execution Logs),stickyfor scroll views — both visually identical to the standard.
Nested sections
Use breadcrumb navigation when a section has its own tabs. Render
WorkspaceViewBreadcrumbs as the shared header title: its Back action returns
to the parent section picker. Only the current section's tabs appear in
WorkspaceViewHeader.tabs; do not stack parent tabs above child tabs.
Integrations uses the shared IntegrationSectionPicker and
PROJECT_INTEGRATION_SECTIONS for Tools & secrets, Slack, WhatsApp, Google apps
and Use in AI apps (subject to product permissions). Each entry has a small icon
and one brief description explaining its destination. Tools & secrets contains
MCPs, secrets, skills and shared Vault access; Use in AI apps connects an external
AI client to AgentWorks. Avoid ambiguous parent names such as Connections or
Connect. The current section's name stays in the header breadcrumb.
Inside Tools & secrets, one header row contains Connected, Available, Secrets,
Skills and Vault. ProjectPluginsPanel accepts the controlled header tab and renders
content without another tab row. Crew, Code, workflows and Relay reuse it.
Connected lists project MCPs first and authorized Vault MCPs below, with one shared search and refresh. Vault MCPs remain available automatically. Secrets shows project secrets followed by permitted Vault secrets, using the same selection component without another source-tab row. Project and Vault secret selections remain independent, including when their names are identical.
Chat navigation
The shared Global Monitor sits beside workspace tools in workflows, Relay, Crew and Code. The top header keeps it on project lists and global pages; an open project workspace owns the instance so it is not shown twice.
Ctrl+K and Global Monitor use openGlobalTab / openGlobalActivitySession.
Direct chat-tab selection uses the same activateTab coordinator, with
workflow tabs committed through activateWorkflowTab. A direct schedule/main
tab click invalidates pending asynchronous navigation even within one workflow.
Scheduled-run tab labels use the scheduler-stamped session title, falling back to the saved job name before the owning workflow's name. Ctrl+K and Global Monitor must preserve that schedule identity when reopening its tab. Keep timing and weekdays in the stored label; the shared tab component handles visual overflow without changing the title between navigation paths.
Formatted transcripts own their scrolling. New conversations open at the latest message; revisiting one preserves deliberate reading position and expanded tools. Reading state is scoped to both tab and session, so a reused schedule tab does not inherit its previous run's position. Navigation must not force a bottom scroll; explicit sends and “Jump to latest” still do.
“Load earlier messages” belongs inside the transcript's virtual scroller, above its oldest loaded row. It must not be a fixed banner that resizes the viewport on tab switches. Older pages and their pagination cursors stay together, scoped to the conversation. The shared event buffer's cursor describes only its own bounded tail, never another reader's locally loaded older pages.
Revisiting a formatted chat uses the same compact/full history projection as initial restore. A refresh replaces the overlapping durable tail, preserves already-loaded rows before that page, and retains their backward cursor. A smaller refresh page must not replace a complete transcript with a partial one. If the memory limit trims the retained prefix, paging resumes from the oldest row actually kept in the shared buffer.
Schedule/trigger restores share concurrent requests, establish the live-event cursor before SSE, and preserve events received while history was loading. Opening an already-loaded run uses its cached transcript; background events continue through the existing live transport.
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 sharedWorkspaceViewIconButtonrefresh. - Ask AI copy is user-visible plain words: no tool names, skill paths,
or config keys. Builder messages use the
workspaceAskAImarker blocks (visible summary + hidden builder instructions), one per view inWORKSPACE_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, nevermd:block. Responsive variants win over base utilities, somd:blockwould overrideflex, collapseflex-1scroll regions to content height, and freeze pane scrolling with no error. - Scroll invariant: every
overflow-y-autokeeps a definite-height ancestor chain in all modes (see Scroll). - Narrow viewports stay single-pane:
focusedPanepicks 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-semiboldtitle, 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
Buttonfor the page's main save;outline/ghostsmfor secondary actions;ghosticon(h-7 w-7) for row actions withtitletooltips. - Pills are kit
Badge(default/secondary/outline), not hand-rolled spans — exceptSettingsCountfor 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.
Shared sidebar and product actions
- The shared icon rail is 48px wide with 36px tall controls and 6px side padding. Keep product icons and hover/focus flyouts unchanged when adjusting its density.
- The product picker shows the current product icon; hover, click, or Arrow Down opens the permitted product list beside the rail. Escape or leaving the picker closes it.
- The bottom utility section is shared across products: active work, Providers, Connect an AI agent (MCP), Users & access (administrators), and Account.
- Activity belongs to Goals/workflows. Schedules and triggers belong to Goals/workflows and Crew, and sit with product actions above the shared utilities. Code and Vault do not show schedule actions. Relays are API-only and do not support timed schedules (PLAT-433).
- Schedules uses one row, Schedules · Triggers, scoped to the current product. Crew uses product schedules; Goals/workflows filter workflow records and exclude Relays.
- Full-page utilities and Activity use
WorkspaceBackButton; returning closes overlays and restores the product workspace. - Users & access reuses the Vault directory panel. UI invitations grant Vault only; other product access continues through platform provisioning.