# Web visual interface

> Canonical path: `specs/web/visual-interface.md`
> Status: active
> Scope: shared AgentTeam Browser/Desktop product visual grammar, interaction hierarchy, compact navigation, and workspace chrome.

## Product Visual Grammar

AgentTeam's product interface follows **Quiet · Precise · Alive**. Quiet means the
working surface is restrained and does not turn every group into a card.
Precise means hierarchy, alignment, density, and interaction states are owned
by shared semantics rather than call-site corrections. Alive means important
actions and live state remain immediately discoverable through a small amount
of intentional color and motion; it does not mean decorative glow, gradients,
or constant animation.

### Density, shape, and icon rhythm

The product layout uses a 4px spacing foundation, with `4`, `8`, `12`, `16`,
`20`, `24`, and `32px` as the normal rhythm. Primary workspace headers are
`62px` high with a `20px` horizontal content inset; secondary headers and tab
bands are `52px` high. Compact controls use `28px`, standard controls use
`32px`, and comfortable input controls use `40px`. Repeated single-line
level-2 navigation rows target `32px` without compressing their text line box.

Corner radius communicates role rather than decoration: micro labels and code
use `4px`, controls use `6px`, repeated navigation selection and light semantic
groups use `8px`, and overlays use `12px`. Avatars, presence indicators, and
true pills remain fully rounded. A page must not introduce a new radius merely
to make an isolated element look more designed.

Icons use `14px` for compact metadata and disclosure, `16px` for controls,
tabs, and navigation, and `18px` for section or entity identity. Icon geometry
does not substitute for a stable slot: shared rows and controls own fixed icon
slots so mixed icon assets remain on one centerline.

### Color and interaction hierarchy

Neutral foregrounds communicate information hierarchy. Strong and default
foregrounds carry identity and readable content; secondary and tertiary
foregrounds carry supporting and metadata content. Inline destinations and
other text-first navigation use the semantic Link foreground so they remain
discoverable in Light, Dark, and custom schemes. High-emphasis filled actions
use Primary; Link and Primary are separate roles and must not be substituted
for one another. Status colors are reserved for actual success, warning,
failure, information, or mention meaning.

Every interactive primitive exposes a stable ordering of resting, hover,
selected, pressed, focused, and disabled states. Hover is a light transient
change, selected is a persistent stronger surface, pressed is the strongest
transient surface, focus uses the shared focus ring, and disabled controls must
not retain an enabled foreground in descendants. Interaction states must not
depend on hover-only text, color alone, or page-specific opacity arithmetic.

Micro-interactions transition only the properties that communicate the state,
normally within `120–180ms`; overlays may use `160–220ms`. Theme changes remain
atomic palette commits as defined below. New product UI must not use
`transition-all`, hover translation, hover scale, or ambient glow as a generic
quality effect, and reduced-motion preferences remain authoritative.

Passive structural dividers between adjacent workspace regions or repeated
content rows use the shared subtle divider color and a `0.65px` rule. Callers
must use the shared `structural-divider-*` or `structural-divide-*` utilities;
the width and color remain owned by the global structural-divider tokens rather
than feature-local literals. This treatment applies to level-2 pane boundaries,
primary and secondary Header boundaries, tab and toolbar bands, docked
side-panel boundaries, menu and dialog sections, table rows, simple list
separators, and tree or timeline hierarchy guides. Control outlines, focus
rings, active tab indicators, resize feedback, and semantic table or card frames
retain their own stronger geometry and color; callers must not weaken those
interactive edges by substituting the passive divider treatment.

### Composer action hierarchy

Conversation composer actions expose their immediate behavior without adding a
one-item menu. When attachment upload is the only available tool, the composer
renders a direct paperclip button that activates the existing file input. When
task creation, agent-thread routing, or another tool is also available, the
composer retains the shared `+` menu and all available options. Both modes keep
the attachment count, disabled state, accessible label, and existing upload
flow.

Emoji and direct attachment triggers are quiet icon buttons. Their resting
state has no persistent circular border or filled background; hover, pressed,
focus-visible, and disabled feedback remain clear. Removing resting chrome must
not reduce the clickable target, replace the shared focus ring, or remove
capability-dependent tools.

## Compact Navigation Rows

Compact navigation rows include sidebar section headers, sidebar item rows, tree rows, toolbar rows, and narrow pane headers that place icons, titles, counts, badges, or actions on one horizontal row.

The level-1 workspace rail starts with one global quick-create trigger immediately before Inbox. The trigger uses a high-contrast add icon with a circular hover target and remains background-free at rest. It opens an anchored menu to the right without changing the active navigation category and reuses the current workspace's existing creation flows for Agents, Agent Teams, Channels, and Agent Hosts. Knowledge-base creation appears only when both MeshX and the knowledge-base feature are enabled. Member invitations remain in people-management surfaces rather than this menu, and other actions the current user is not allowed to perform are omitted. Selection, Escape, and outside interaction dismiss the menu through the shared menu primitive.

These rows must align every visible icon, text run, count, badge, and trailing action to a shared vertical centerline inside the row container. Text must not be positioned by browser baseline defaults when it appears beside an icon.

Required implementation shape:

- The owning shared primitive defines the row height, leading icon slot, text slot, count or badge slot, and trailing action slot.
- Leading icons and trailing icon buttons use fixed flex slots with `items-center` and `justify-center`; SVGs render as block content inside the slot.
- Single-line titles use an explicit text box that is vertically centered in the row. The glyph-bearing overflow element must leave compensated block-axis breathing room outside its line box while horizontal overflow remains truncated; do not use font-box trimming or clip directly at the line-box edge because mixed-script fallback glyphs can extend beyond it. Counts and single-line trailing labels use the same centerline.
- Multiline rows keep the leading icon centered against the row as a whole while the first title line uses the same centered title text box.
- Hover-swap controls, disclosure chevrons, counts, unread badges, and action buttons keep stable slot dimensions across hover, focus, active, collapsed, and loading states.

Do not correct compact row alignment with per-language, per-label, per-icon, or per-callsite pixel offsets such as `mt-*`, `top-*`, negative margin, or `translate-y-*`. If alignment is wrong, fix the owning shared primitive or the icon asset/viewBox.

For workspace level-2 sidebars, `web/src/agentteam/workspace/sidebar/sidebarPrimitives.tsx` owns section headers and item rows. Product panels should consume `SidebarSectionHeader` and `SidebarItemButton` instead of rebuilding icon/title/action rows locally. A custom level-2 sidebar row may exist only when the shared primitive cannot express the interaction; it must still follow the same slot and centerline contract.

Workspace level-2 sidebar typography follows one semantic hierarchy:

- Clickable item and resource titles use `14px` type on a `20px` line box. Normal rows use regular weight; selected, unread, pending, or otherwise emphasized rows use medium weight.
- Section headers use `13px` type. Text metadata beside a title, such as a short role or state label, uses `11px` type. Badges and unread counts remain compact at `10px`.
- Relative or absolute timestamps use `12px` type on a `16px` line box and the subdued metadata foreground treatment, whether they appear on the title line or the secondary line.
- Secondary summaries use `11px` type on a `16px` line box and the subdued metadata foreground treatment. A semantic text state may precede the summary at medium weight with a shared tone and a centered-dot separator; callers provide the state value and tone but not typography or color classes. Status icons and badges occupy their own semantic slots rather than being combined with summary or timestamp strings.

Product panels provide semantic strings and status content to the shared row primitives; they must not provide title, timestamp, or secondary-summary font classes or construct custom title-line markup. `SidebarItemButton` and `SidebarResourceRow` own this typography. An expandable level-2 tree must extend the shared row contract rather than establish a separate title scale. New metadata that does not fit the existing title metadata, badge, timestamp, status, summary, or secondary timestamp slots requires a shared primitive contract instead of call-site layout.

Expanded level-2 sidebar child guide rails must derive their inset from the parent row's leading icon slot: the rail axis is the parent title icon centerline, and child item fill/padding values are derived from the same row padding and icon-slot variables. Child avatars start on the parent title text axis; trailing status indicators use the same action-column slot as section header icon actions. Do not encode independent rail and child padding constants. Guide rail color must use subtle foreground/border tokens with soft endpoints so hierarchy is visible without creating a heavy divider.

Workspace detail navigation uses Router location as its single source of truth. Selecting a known Inbox conversation, Channel, Agent, Human, or Workflow immediately updates the URL, the sole selected sidebar row where present, `aria-current` where present, and the destination Header and Tabs from the descriptor already visible in the source list. Header and Tab controls remain mounted and interactive while destination data resolves.

Authorization and data readiness gate only the protected body data and actions that require them. Conversation timelines own their cache-first loading, error, retry, and empty states; the workspace shell must not add a main-pane loading overlay, retain the outgoing route as the visible Surface, or make Header and Tabs inert. Exact route keys prevent one destination's cached body from appearing under another destination's chrome, and route generation prevents late mutation completion from publishing local effects into a newer route. Unknown external routes may resolve their descriptor and access explicitly, but that resolution path must not delay navigation for a descriptor already present in a workspace list.

Normal-flow workspace chrome that shares a conversation's vertical viewport must commit its route-scoped presentation before the timeline's final initial positioning. Background refreshes retain that committed presentation, and data loaded only for a secondary surface must not insert a first chrome row into an already-ready conversation viewport. This layout handshake may delay the timeline's final positioning and reveal, but it must not delay route identity, Header, or Tabs mounting.

Message presentation must not change history pagination semantics. Collapsing
consecutive messages from the same App is a client-side rendering decision and
must not trigger ready-state older-page requests based on viewport fill or
transient virtual-list measurements. A conversation may perform at most two
bounded older-page requests before its first ready paint when the measured
first page cannot form a scrollable surface; this entry bootstrap finishes
before final initial positioning and stops on scrollability, exhausted history,
no progress, failure, or the page bound. Explicit message targets bypass entry
bootstrap. After the timeline is ready, older history loads only when the user
navigates toward it or invokes the explicit load action. If every render item
in the currently loaded message window is an App-message group, those groups
expand by default so the conversation remains readable; explicit user collapse
or expansion remains authoritative.

Direct Inbox Channel, DM, and Thread routes share one `InboxConversationFrameHost` mounted outside Router and reply-thread layout branches. That host owns the primary Header and Tabs containers; route components publish only their row content, secondary strips, exact-scope body, and sidebars into the frame. Fullscreen, docked, mobile, no-thread, loading, and error states may replace body content but must not replace or remount the Header or Tabs containers. A cold reload or deep link synchronously creates a capability-closed minimal display descriptor from the route until the exact Inbox row or authoritative snapshot replaces its labels. The descriptor can render identity but cannot authorize tab actions, composition, socket membership, sharing, member management, or other protected behavior.

## Shared Selection Controls

Compact mutually exclusive choices use the shared segmented-control primitive. The group renders as one quiet neutral track; inactive options remain visually flat, while the selected option receives the only raised surface, stronger text, and fine semantic outline in the group. This contrast must remain immediately recognizable in Light, Dark, and custom color schemes without relying on ambient glow, gradients, scale, or saturated full-fill color.

The shared primitive owns compact, default, and comfortable control heights, item insets, corner radii, focus treatment, and transition timing. Product call sites choose a supported size and may control width or item distribution, but do not rebuild selected-state styling or replace the primitive's geometry with local height, padding, radius, or type-size values.

Menus, selects, context menus, and picker results render on the raised surface and use raised-surface interaction states rather than content-surface row states. Hover is immediately distinguishable from the resting raised surface, selected is a stronger persistent state, and pressed is the strongest transient state. Shared menu primitives own this ordering in Light, Dark, and custom color schemes; product call sites do not add mode-specific opacity or neutral-color overrides.

## Workspace Pane Tabs

Workspace detail pane tabs live in secondary headers and use `WorkspacePaneTabs` backed by the shared underline tab primitives. Tab labels and their leading icons must remain on one horizontal centerline and must not wrap, stack, or shrink into vertical text when the pane narrows. The tab strip owns horizontal overflow; constrained layouts should scroll the tab row or move low-frequency actions into an overflow menu instead of compressing text or changing icon size. When overflow hints are shown, their arrows must be keyboard-accessible controls that page the shared scroller and disappear at the corresponding scroll boundary.

The Workflow module uses this tab row as its collection navigation and does not
open a workspace level-2 sidebar. Its tab order is Overview, Workflows,
Schedules, and Runs. The Overview uses a responsive two-region layout: a
three-day Run calendar is primary and a current-window health summary is
secondary. The calendar keeps three chronological day columns without a
separate time-axis column or proportional empty time gaps. Its shared internal
scroll reveals an overlay scrollbar only during scrolling, so scrollbar
visibility never changes column width. Wide panes fill the remaining workspace
height and keep the two regions side by side. Narrow panes retain all three day
columns, then stack the health summary below the calendar without changing the
date window or tab order. The dashboard health-heading row shares the calendar
toolbar's height and vertical centerline, without adding a divider beneath the
heading. Its four headline metrics share one outer border with internal
two-by-two dividers. Success rate uses completed outcomes only:
`succeeded / (succeeded + failed)`. Queued, running, waiting, and canceled Runs
remain in the total Run count but do not enter that denominator; when the window
has no succeeded or failed Run, the success-rate metric displays an em dash.
The calendar uses only its internal day and column dividers, without an
enclosing frame or a duplicate totals footer.

## Workspace Primary Headers

Primary workspace collection and module headers use one horizontal hierarchy: `Title / Description`. The shared header owns a 20px horizontal content inset, truncation, mobile sidebar entry, and trailing actions. Decorative icons do not precede these titles. Identity detail surfaces may retain an avatar when it identifies the selected human, Agent, service, or Machine; identity avatars in this header tier use the shared 36px size token across resource types.

First-level module identity headers use the same `WorkspaceModuleHeader` whether the module owns a level-2 sidebar or opens directly into the content pane. The component owns the full 62px header geometry: a 12px horizontal inset, a 32px bordered icon tile with a normalized 15px icon, a stacked 14px semibold title over an 11px tertiary description, the mobile sidebar entry, and trailing actions. Sidebar-specific CSS must not resize or recolor this identity subtree. Content collection and detail headers remain separate semantic tiers and must not recreate the module identity header with page-local spacing.

Named management details use the three-part hierarchy `Domain / Name · Description` when a domain label and selected subject are both available. Apps Integration details use `Apps Integration / App name · Description`; service-account details use `Service account / Account name · Description`. The domain label remains first so sibling detail surfaces keep a stable navigational theme. Management settings fill the remaining content-pane width inside the shared responsive viewport padding instead of choosing page-specific centered or fixed-width tracks.

Detail hierarchy should be navigable through its readable parent label. A Workflow Run header uses a clickable Runs parent followed by the human-readable Workflow or Trigger name; opaque internal run IDs do not serve as the primary title. Stable identifiers may remain in secondary metadata or diagnostic actions.

Full-page detail headers with a leading back action use a 12px horizontal inset and a 12px gap after the 32px back button. Loading and loaded states preserve this geometry. Headers without a leading back action retain the inset defined by their own semantic tier.

Workflow, Schedule, Run, and full-page Task detail hierarchy follows the Channel and Thread type scale: the domain or container label is 16px semibold, `/` is a quiet separator, and the selected object is 14px medium. Workflow and Schedule descriptions follow a quiet `·` on the same row and truncate before trailing actions. Clickable labels retain the normal foreground and add an underline on hover instead of switching to Primary. These detail headers do not introduce a second metadata line.

Workspace collection card grids respond to the width of their content pane rather than the browser viewport. Workflow collections use one column in narrow panes, then two and three columns as the pane grows, and reach four columns only when the content pane is at least `80rem` wide so the cards retain their readable minimum width.

The Workflow collection exposes lifecycle filtering as a compact single-select radio group ordered All, Active, Archived. Active remains the default selection so the primary collection excludes archived definitions until users explicitly request them.

Workspace collection Headers and Filter Bars remain mounted while their collection data first loads, filters, searches, or changes page. Loading replaces only the collection body with a low-contrast skeleton that matches the eventual row or card structure, so stable navigation and controls do not flash or shift. Workflow collections request one bounded server page at a time and place previous/next cursor navigation at the Filter Bar's trailing edge; they do not fetch the complete collection before first paint or imply a total page count when the API does not provide one.

Workflow collection cards use one compact identity block: the Workflow icon occupies a fixed leading slot; the adjacent Card Header places the readable title on the left and aligns the complete version/lifecycle badge group to the Header's right edge; and the next line displays the stable key as subdued monospaced metadata with a fingerprint icon. Long titles truncate before the right-aligned badges. The optional description follows this identity block and displays at most three lines. A dedicated footer identifies creation recency from the Workflow creation timestamp and provides the open affordance; it does not present last-update activity as creation time. Collection cards only select and open a Workflow. Management actions such as duplication belong to the Workflow detail Header.

## Desktop Workspace Chrome

Desktop may use one native translucent window backdrop as the Liquid source beneath the application shell. The native host owns material selection: macOS uses a window-wide sidebar vibrancy that follows active-window state, while supported Windows versions use the long-lived-window Mica material. Do not use transient Acrylic for the application frame or add CSS `backdrop-filter` blur on top of the native material.

The native host uses one `BrowserWindow` whose primary renderer fills the native content bounds. Renderer sizing belongs to the native window layout; do not translate window resize events into deferred `WebContentsView.setBounds()` calls. The renderer root remains transparent so the title bar and `66px` level-1 rail reveal the shared native material. The first opaque level-2 or main workspace surface begins below the `40px` title bar and owns the `12px` top-left inner radius. When native material is unavailable, the `BrowserWindow` background provides the mode-resolved solid fallback beneath the same renderer geometry.

Keep the renderer's live-resize plane unmasked and unclipped. The first opaque level-2 or main surface owns ordinary top-left radius and overflow clipping; do not apply a full-shell CSS mask merely to create the corner. Native window composition owns resize continuity, while CSS owns the settled content geometry.

Desktop must synchronize the application's selected Light, Dark, or System appearance source and resolved solid fallback color to the native host before revealing the window and whenever that selection changes. The operating system remains authoritative only while AgentTeam uses System appearance; an explicit AgentTeam appearance remains authoritative. When returning from an explicit Light or Dark selection to System, Desktop releases the native override and applies the native host's resolved Light or Dark result; renderer media-query propagation is not an acknowledgement boundary and must not leave the previous explicit mode latched.

Appearance changes are atomic palette commits, not animated page transitions. Light, Dark, System, and custom color-scheme changes must update the root mode and semantic color tokens together while temporarily suppressing component-level CSS transitions. Controls keep their ordinary hover, focus, and press transitions before and after the commit, but they must not interpolate old and new theme tokens or expose a mixed-palette frame during the change.

The title bar, the level-1 sidebar rail, and the exposed fill inside their shared concave corner have a hard same-material invariant. They reveal one continuous native backdrop instance through one mode-resolved neutral renderer tint with the same opacity and compositing path. Light mode adds a restrained white tint and Dark mode adds a restrained black tint; custom color schemes do not recolor the native material. Native material may vary spatially with the desktop behind it; that variation is part of the shared material rather than a color mismatch. Spatial gradients, per-surface overlays, independent opacity, separate background values, and separate native material instances are forbidden across these three positions. When native material is unavailable or reduced transparency is requested, all three positions fall back together to one uniform solid chrome fill.

These three positions form one continuous Liquid chrome plane. They meet without a seam or divider and read as one L-shaped app frame rather than two adjacent panels. The first opaque workspace surface owns the inside corner radius; the live-resize row itself remains unmasked and unclipped. The same-material invariant applies in Light, Dark, every custom color scheme, active and inactive window states, and while a modal backdrop is present. Main panels and level-2 sidebars remain ordinary opaque rectangles over the window-wide native material or its solid fallback.

The boundary between the Liquid chrome plane and the opaque work surface relies on the surface geometry and native edge antialiasing. Do not add a rim, divider, shadow, gradient, or blur along this boundary. Chrome controls may use restrained color transitions, but must not translate, scale, or add ambient glow merely to imply depth.

The global search trigger is a control placed on the chrome plane, not another application surface. Its resting and hover fills derive from neutral contrast within the shared chrome material so they remain gently inset and visibly bounded in Light, Dark, and custom color schemes. Focus uses the shared theme focus ring, while the shortcut hint steps back toward the neutral chrome surface. The search trigger must not reuse selected or pressed row colors as its permanent fill, because that confuses an idle control with navigation state and can collapse its contrast against the chrome.

The opaque level-2 sidebar and the main workspace use the same theme content surface in every color scheme. Their hierarchy comes from boundaries, spacing, typography, and interaction states rather than a second full-pane tint. The Liquid title bar and level-1 rail remain the only translucent chrome plane. Custom color schemes remain visible through their opaque content surface, foreground, focus, and interaction colors without recoloring the Liquid material itself.

The level-2 sidebar, workspace pane headers, tabs, message surfaces, settings surfaces, dialogs, and other primary work areas must render on opaque `background` tokens so text and icons stay sharp. Do not let native glass, CSS `backdrop-filter`, or transparent root backgrounds show through the working surface outside the title bar and level-1 rail.

## Workspace Settings Surfaces

Workspace detail settings pages should present entity metadata and controls as a stable settings surface, not as full-width form fragments stretched to fill the pane.

Settings surfaces should fill the available settings container width. Section frames, basic information groups, collection toolbars, option groups, and tables use the shared content axis and `w-full`-style sizing instead of shrinking to the intrinsic width of their text or controls or sitting inside a narrow centered column. A setting block should feel anchored to the page grid, not like a narrow island inside its parent container.

Settings controls that persist immediately and take effect without a separate Save command must show toast feedback for both success and failure. Do not silently apply an immediate configuration change, and do not rely only on inline transient text or console output for failed saves.

Basic information fields for the same entity, such as a channel name and description, belong in one bordered outer frame. The frame represents one semantic metadata group, so the fields should not be split into separate cards or detached input blocks only for visual hierarchy.

The basic information frame must use the same content axis, spacing rhythm, border radius, and subdued border/background tokens as adjacent settings sections. Controls inside the frame share a clear left edge and edit affordance, and the frame must not contain nested cards.

Choice groups such as channel visibility and discovery do not need a heavy outer frame when each option is already rendered as a bordered selectable tile. Use either one subdued fieldset frame with simple option rows, or bordered option tiles inside a borderless section. Do not combine a large bordered group container with bordered child tiles, because the double frame creates card nesting and weakens the settings hierarchy.

Collection settings such as channel administrators, invited people, agent members, and access rosters must render as a compact table when they contain repeatable members, roles, states, or row actions. Keep the table structure visible even when the collection is empty; render the empty state inside the table body instead of replacing the table with a standalone card or blank hint.

Every data table must render a visible header row with localized column labels. Do not hide table headers with `sr-only` styling or replace them with labels embedded only inside body rows. Loading, empty, and error states keep the same header and render their state row inside the table body. Row-action columns still need a concise visible header such as `Actions`.

When a collection table has a filter bar, search inputs and filter controls sit above the table on the left. Primary actions that change the collection, such as inviting people, adding agents, or adding administrators, sit above the table on the right. The toolbar and table share the same content axis.

Person, agent, and app pickers used by collection actions should open a dropdown whose body contains the search input and bounded results. Opening the dropdown with an empty query should show the first bounded candidate page instead of a large hint-only body; the first visible page is capped at 20 candidates. Search must combine server-side querying for remote candidates with client-side filtering for already loaded or local candidate groups. Do not render an unbounded full dropdown; use backend cursor pagination where the source can grow, and place a Load more button at the bottom of the dropdown when more candidates are available.

Picker dropdown bodies use natural content height with a viewport-aware maximum height. Do not give the results body a fixed height that leaves empty space below short result sets. Empty, loading, and error states should be compact rows inside the body, not tall placeholders that make a short dropdown look like a blank panel.

Surface elevation remains perceptible when overlays are nested. Page content uses the content surface, blocking dialogs and sheets use the raised surface, and shared transient menu-like overlay primitives default to the floating surface. The floating surface is derived from the active theme rather than hardcoded per component, so Select, Dropdown, Context Menu, Popover, Searchable Select, and shared floating panels remain subtly distinct from both a containing dialog and the page behind it in every color scheme. Larger tool panels may explicitly use the raised surface when they represent a persistent work area rather than a transient menu.
