Chrome Agent Platform — Component Gallery

Every UI element as a native Web Component, testable in isolation (mocked backend, no extension). The extension pages use these same components.

Atomic components

<first-run-guide> — the first-run banner

One sentence, one action. Shown above the hub composer only while no model is connected; with provider-ready it renders nothing. The dismiss control is last in the tab order. It never requests a permission itself.

<example-chips> — example tasks under the composer

A chip emits pick with its text; the host prefills the composer and focuses it. A chip never runs anything.

<run-task-button> — states

default · loading · disabled · custom label. Emits run-task.

<mic-button> — speech toggle + waveform

idle · listening. Emits transcript and mic-toggle. (Web Speech runs in-browser; headless has no mic — the toggle still works.)

<attach-button> — the + menu

opens the file / record-audio / capture-camera menu. Emits attach and attach-media.

<theme-picker> — swatches

clicking a swatch re-themes this whole page + emits theme-change.

<permission-row> — the all-optional capability rows

each capability is optional; Enable/Disable emits enable/disable.

<origin-grant-row> — one allowed origin of the browser-control set

Settings → Browser control lists every allowed origin with its own remaining time; Turn off emits revoke with { origin } and leaves the other rows alone.

<capability-row> — a recipe/capability (icon | name+desc | action)

name + description are stacked (never run together); action right-aligned on a 28px|1fr|auto grid. Background agents use open-delete (chevron + Delete) — the enable/disable toggle was the wrong primitive.

Settings → Permissions: action-state="on" puts the shared switch in the action column (its toggle is re-emitted from the row); action="run" action-label="Turn on" is the ghost request button (the click is the user gesture); action="state" is text only; detail hides the "Gates:" sentence behind a disclosure.

<switch-toggle> — the direct enable/disable switch (permission rows and other non-agent controls)

36×20 visible switch — the blank-toggle regression pin lives here.

<site-agent-card> — a per-site sub-agent

emits select on click/keyboard (with the exact tabId in the offer variant — the hub's "offers N tools — use them?" chip; check is the chip before the one-time scripting grant, emitting { check: true }; using is the same chip after the grant).

<agent-template-card> — an agent starting point

bounded persona + three skill badges + overflow; curated starters carry the Starter badge; selected presses the Use button (aria-pressed); blank is the "Custom agent" card; the whole card activates; emits use.

<agent-template-gallery> — the template catalogue as a filtered grid

one tab stop (roving tabindex; arrows / Home / End move, Enter or Space activates); Starter / All / Scheduled filters with counts; the selected card reflects what the form currently holds; emits use and filter-change.

<tool-directory-card> — one registry function with attached states

semantic name → description/schema → source/approval order; the card reflows from wide to narrow without detaching badges.

<artifact-card> — a generated artifact (the gallery card)

emits open / reuse / delete. The live preview is set via the preview property (see the demo below).

<artifact-diff> — what changed between two versions of an artifact

unified · split (collapses back to unified under 720px of container width). +n -m · k changes in the header; n/] next change, p/[ previous, focus moves to the hunk and a polite live region says "Change N of M". Every line is untrusted model output: rows are DOM-built with textContent after bidi/control neutralisation. Rendering stops at max-lines (2,000) with an honest note. Emits navigate and truncated.

<segmented-control> — a quiet tablist for switching views

Preview selected. role="tablist" with role="tab" buttons and roving tabindex; / (and /) move and select with wrap, Home/End jump to the ends. Emits change {value} (never on a no-op re-select). Labels enter via textContent. The artifact viewer mounts one for Preview | Source | Diff. With a controls-prefix attribute each tab also gains a stable id + aria-controls pointing at the host's tabpanel of the same label-slug — the pattern the Settings → Providers family tabs use (the host owns the panels and toggles hidden on change).

<artifact-inspector> — the exact, bounded, highlighted source

Read-only inspection: the exact body set with textContent, bounded to 64 KiB, with a dependency-free tokenizer (highlightSource) that colours html/css/js/json into tok-* spans built with the DOM API — no highlight.js, no new Function. "Copy exact content" copies the complete artifact; an HTML artifact also offers a sandboxed "Preview / Play".

<artifact-quick-drawer> — bounded recent/search access

metadata-only drawer with native light-dismiss, focus return, owner/type/size/time facts, search + type filtering, Open/Reuse events, and a Browse all artifacts event. Recent DOM is capped at 8; filtered results at 40.

<message-bubble> — conversation turns

role carried by alignment + surface — user right, agent/thinking/tool/error left; markdown + code + tool cards render inside agent/system. HTML output (a full document or a block-level fragment) renders in a SANDBOXED iframe, not as escaped text. A tool card's tool-detail is the COMPLETE retained result (redacted, ≤ 64 KiB): one collapsible result tree, a syntax-coloured pretty JSON view (JSON), Copy of the full pretty text, and Show all to lift the inner scroll; a failed call — including an error nested inside a lazy ok:true envelope, or a bare protocol code — headlines the error in the error colour; a result that hit the cap says so in tool-detail-note.

<agent-identity> — who + when on a turn

the one identity header for a conversation turn: a 24px avatar (the agent's generated avatar via avatar, otherwise an inline-SVG initial in the accent), the name, and a <time datetime> stamp ("just now", "3m ago", a clock time). <message-bubble role="agent" author ts> renders it above the bubble.

<agent-conversation> — the unified surface

renders a full turn: user message (with a fenced code block), a collapsible thinking trace, structured tool cards (done + error), and a system response with a code block + inline code + a list.

<screenshot-strip> — generated-image strip

empty state, then a populated strip. Pass a JSON array of {url,label,kind} (kind = screenshot|image steers the label); max caps the visible thumbnails and adds a "+N" overflow. Emits open with {index}.

<screenshot-thumb> — one saved capture

the tool card's screenshot. In the extension it resolves the PNG from the screenshots store by shot-id and revokes its blob URL on disconnect; src short-circuits that for this specimen. The alt names the page, never just "screenshot".

<error-console> — the terminal-like error log

a badge (count) + a floating terminal panel of the captured errors/warnings (errors sorted first, newest first within a level). Clearable; each line has a Copy button + a Copy all in the header; Escape / outside-click closes. In the extension it reads the live buffer; here it degrades to the empty state.

<security-shield> — the security transparency surface

a shield icon + the security posture: granted optional permissions + CSP/denied-hook/blocked-action violations. An attention attribute flags violations.

<diagnostics-panel> — the hub diagnostics overlay

an activity icon + floating overlay showing key platform metrics (running agents, completed tasks, captured errors, tool calls) and recent error logs without navigating to Settings.

<privacy-statement> — "What this extension sends and stores"

the one privacy page (Settings → About → Privacy). One property, statement, in the shape buildPrivacyStatement() returns; every row is data rendered with textContent. The storage names sit behind a closed disclosure.

BeautifulUI-inspired primitives

<loading-state> — the pixel-grid loader + elapsed time

a calm pixel-grid working indicator (not a generic spinner); active animates, elapsed shows the time.

<conversation-run-status> — the single conversation lifecycle surface

one atomic live region for queued, running, retrying, waiting-for-permission, completed, failed, and cancelled; the pixel grid animates only during active states.

<plan-strip> — the running task's plan checklist

the run's tool calls as a checklist pinned to the top of the thread — the current step active (a spinning ring), completed ones checked; on done it settles into a collapsed “N steps” <details> the owner can re-open. One visually-hidden aria-live region announces the step in flight.

<thinking-trace> — an expandable reasoning trace

grouping tabs by domain…

a muted, collapsible <details> of the agent's steps — never a wall of text. Emits toggle.

<tool-chips> — tool calls as compact chips

each chip: the tool name + a status dot (done/running/error). Emits select.

<task-row> — a live task-status row

a status indicator (done / running / failed) + the name (ellipsized) + the time. Emits open / delete.

<next-run> — a routine's forward-looking next fire

the "Next run relative · absolute" indicator for a routine (a scheduled/recurring task), computed from its REAL alarm's scheduledTime via the shared nextRunLabel projector. at is the fire time; period marks a recurring routine; last adds "Last run ago"; with no armed fire it shows the label fallback (paused / needs attention).

<streaming-text> — a streamed answer with sources + actions

a live caret while streaming; inline source chips + follow-up actions. Emits action / source.

<approval-card> — a human-in-the-loop approval

the agent asks before acting; the owner Approves or Denies. Emits approve / deny. A script approval sets the detail property ({ source, hosts, dynamic }): the exact source renders in a scrollable, labelled <pre> (textContent), the hosts it fetches are listed, and a computed URL is called out in the danger tone.

<prompt-bar> — the composer with @ sources + / commands + a model picker + dictation

a composer composed from the atomic mic-button + attach-button + a model picker + the @ / popups. Emits send, mention, command, model-change.

Composite components

<durable-run-registry> — retained owner run controls

Native Cancel, Resume and View logs controls with exact run context, confirmation, pending suppression and live feedback.

<agent-composer> — mic + attach + input + send, one component

the SAME component the NTP hub + chat use. Emits send (text + attachments). The textarea is a textbox-with-popup: while a / or @ palette is open it carries aria-haspopup="listbox" + aria-expanded="true", aria-controls naming the listbox, and aria-activedescendant naming the highlighted option (cleared on close); the multiline textarea keeps textbox semantics — never role="combobox".

<agent-dialog> — consistent modal

slotted content + Esc/backdrop close. Emits close.

<agent-picker> — the ONE unified agent picker

grouped (Named / Background / Site) + searchable; combobox→listbox keyboard contract; emits agent-select (canonical ref) + the legacy select for site entries. In the extension it fetches the live agent.registry itself; here it renders the attribute data.

<agent-picker> — states (empty / loading / error)

the explicit empty / loading / error states every host gets for free.

<provider-select> — the shared provider picker

the SAME component the Providers section and the per-agent overrides use. Native select (base-select styled), one 36px control, labeled by default. Emits change.

<model-picker> — the searchable model combobox

the SAME maintained catalogue the Providers section uses (modelsForVendor → llm-prices, newest-first). ARIA combobox: type to filter, arrows + Enter, Escape reverts; an unknown typed id commits as a custom value. Empty catalogue = free-text mode (Ollama / OpenAI-compatible).

<tool-library> — READ-ONLY tool catalog diagnostics (panel-1 slice)

The Settings Tool library component. HARD BOUNDARY: no events, no buttons, no actions (no install/update/revoke/grant/execute/verify/copy) — the only interactivity is the native <details> disclosure. States: ready (summary + diagnostics detail incl. fail-closed rejections and collision exclusions), bounded rows (availability chips: ready/stale/owner-action-required/disabled), loading, error, unavailable, empty. Panel 2 is the static truthful "No bundled Wasm packages are admitted in this build." One polite live region announces each state transition once. Digest values truncate with the full hex in title (no copy control in this slice).

<agent-timeline> — the hub's spine: one reverse-chronological stream of tasks + runs

The hub below the composer (CAP-FB-20260828-HUB-AS-TIMELINE-01): what is in flight, what is waiting on you, and what came back while you were away — one list, not three object catalogs. Each row is a single Open target that emits open with the entry id (a task thread, or an agent run). A leading status dot carries a status word too, so colour is never the only signal. Seeded demo entries — the real surface is the pure buildTimeline projection of the thread index + the durable-run registry. Second instance shows the empty state.

<activity-explorer> — the browsable/searchable activity log

search by text, filter by agent (seeded demo entries — the real surface queries activity.list across master + named + background + site agents). The hub shows user-meaningful rows only (Started / Finished / Failed / Made / approvals / schedule runs); attestation and tool-protocol rows stay in Run logs. The three specimens show the populated, filtered-empty and zero states.

<action-ledger> — the "what I did" surface with Undo

A most-recent-first list of the mutating actions the agents took, each a plain-language sentence. Reversible rows (a closed tab, a group, a bookmark, an opened tab) carry an Undo button; an already-reversed row reads Undone; a mutation with no inverse says Can't be undone. Seeded demo rows — the real surface queries actions.list and Undo calls actions.undo, which re-runs the inverse through the same grant/approval checks the original went through. Second instance shows the empty state.

<jobs-board> — the shared agent-to-agent jobs board

The owner-facing shared work queue, grouped Open / Claimed / Blocked / Settled: every job shows its status word (colour is never the only signal), the poster, the claimant (or unclaimed), and what a blocked job is waiting on. A settled job's result is openable in place (click the row to expand the full result). Seeded demo data — the real surface queries board.list + board.messages and refreshes live on board progress events. Second instance shows the honest empty state.

<system-prompt-editor> — the layered system-prompt viewer + override editor

the SAME component Settings → Advanced uses. Seeded demo data below (the real surface bridges the SW prompt.describe/prompt.set routes). Emits prompt-save / prompt-reset / prompt-keep.

Composed — a mini hub

hub surface (composer + recipes + conversation + agents)

events appear here…

events + states