Skip to content

[Epic] Agent UI as composable blocks — a first-party component library #187

Description

@serge-ivo

An agent's UI has exactly two settings today: take a whole built-in surface, or build everything yourself. Nothing in between. That canyon is why a Repo Coder — which owns one repo and is driven by its Lead — shipped with add-repo, a multi-repo list, and a duplicate way to drive an engine: declaring surfaces:["coding"] was the only way to get a terminal.

Surface options (ba415d4) patch the symptom. The cause is that surfaces are monolithic.

What an audit of the console found

The blocks are already generic, just not extractable.BoardTab literally declares GENERIC_COLUMNS and is mounted for every agent without a dedicated surface. TerminalView + render-terminal.ts (~110 LOC) have zero hooks, zero endpoints, zero router, zero context — fully controlled by props.

And they are already duplicated. The chat thread exists three timesInstanceDetail.tsx, CopilotView.tsx, AgentDetail.tsx — near line-for-line, including the same tool-call regex, the same voice pill, the same scroll-to-bottom threshold, plus CopyButton and playMessage defined twice each. There are five bespoke relative-time formatters, none using the SDK's formatTime. Two kanban boards. Two file lists. The console renders terminal output as an uncolorized <pre> while the coder colorizes it.

Every one of those is a block that wanted to be shared and had nowhere to live.

Proposed order (by extraction value)

  1. Terminal — trivial, self-contained, ~110 LOC. Immediately upgrades the uncolorized <pre> on the Terminals page. Do this first.
  2. ChatThread — biggest payoff: six separate duplications collapse. Needs ~200 lines lifted out of InstanceDetail and a renderMessageBody slot so the gloss layer stays out of the library.
  3. RunList/Timeline — three components converge; props are already just {instanceId} and only lookup tables differ.
  4. Board — one useNavigate to invert into onOpenItem, plus hoisting the apply-retry and BROWSER_TASK run form into props.
  5. FileList + FilePreview — blocked only on relocating UploadJob.
  6. Primitives that fall out: Modal, CopyButton, EmptyState, StatusPill — and delete the five time formatters.

There is a precedent to copy: agents/coder/web is already a workspace package with React as a peer dependency.

Blockers to settle first

  • Theme tokens live only in the console (@theme in index.css). Every candidate block is saturated with bg-panel border-line text-accent. A package built outside the console emits classes with no definitions.
  • Global CSS the components silently need: .msg-md, .chat-scroll, scrollbar-none.
  • The Tailwind cross-package scan is a one-off hack — a literal @source "../../../agents/coder/web/src". Every new package needs another line, and no @source can ever cover a third-party bundle built outside this repo.
  • alert()/confirm() inside candidate components must become injected confirm/toast.

The decision that shapes everything

See #187. Whether third-party surfaces consume React components or a DOM-mount API determines the library's entire public shape, so decide it before writing component code.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    P3: laterDesign, strategy, or deferred pending a decision or demandenhancementNew feature or requestfrontendFrontend / UI work

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions