Yugen UI

Process transparency with Trace

When the system works on the user's behalf (AI agents, pipelines, background jobs), show each step: what it is doing, how long it takes, where it failed and what can be done next.

Why transparency matters

A “Processing…” spinner that lasts 20 seconds makes users doubtful: they click again or leave. A list of steps that keeps moving does not.

  • Trust: users see which documents the AI read and which tools it called before they rely on the answer.
  • Control: knowing the current step lets them decide whether to wait, stop or retry.
  • Debugging: when something goes wrong, the failed step and its log are right there, with no other tab to open.

Six states

Each state has its own icon, its own color token and a text label that screen readers read. Color is never the only signal.

StateLabelWhenColor
pendingĐang chờ (Pending)Planned, not started yet.text-foreground-3
runningĐang chạy (Running)In progress; the timer runs live.text-brand-foreground-1
successHoàn tất (Done)Finished, with a duration.text-success-foreground-1
errorLỗi (Error)Failed. Details open by themselves and state the cause.text-danger-foreground-1
skippedBỏ qua (Skipped)No need to run (condition not met, cache hit).text-foreground-3
cancelledĐã huỷ (Cancelled)Stopped by the user or because an earlier step failed.text-warning-foreground-1

Choose the level of detail

One step is one task the user understands. Technical details go in the expandable part, not in the title line.

Create the October report

Pending

Duration: 9.6s
  • 2.1s
    sales_db12,480 rows
  • 3.4s
  • Write commentary, Completed
    4.1s
    sakura-12.1k tokens
  • A step title is a verb plus an object (“Fetch sales data”); function and tool names go in metadata or details.
  • Nest at most 2 levels. If the agent makes 30 small calls, group them under one parent step “Read 30 files” with collapsed children.
  • metadata is for information you scan: tool, model, token count, row count, git branch.
  • details (logs, JSON, CodeBlock) are collapsed by default; failed and running steps open by themselves.

Live updates

Trace only displays data. You keep the list of steps in state and update it from the agent's events.

// Agent events (SSE / WebSocket) → TraceStep props
type AgentEvent =
  | { type: "step_started"; id: string; title: string; at: number }
  | { type: "step_finished"; id: string; at: number; tokens?: number }
  | { type: "step_failed"; id: string; at: number; error: string };

function reduce(steps: Step[], e: AgentEvent): Step[] {
  switch (e.type) {
    case "step_started":
      return [...steps, { id: e.id, title: e.title, status: "running", startedAt: e.at }];
    case "step_finished":
      return steps.map((s) => s.id === e.id ? { ...s, status: "success", duration: e.at - s.startedAt } : s);
    case "step_failed":
      return steps.map((s) => s.id === e.id ? { ...s, status: "error", error: e.error } : s);
  }
}

<Trace title="Agent" startedAt={runStartedAt}>
  {steps.map((s) => (
    <TraceStep
      key={s.id}
      title={s.title}
      status={s.status}
      startedAt={s.startedAt}
      duration={s.duration}
      details={s.error && <TraceLog intent="error">{s.error}</TraceLog>}
    />
  ))}
</Trace>
Prop · TypeDefaultDescription
statusTraceStatusDefault: "pending"Changing status is enough: the icon, color, timer and announcement follow.
startedAtnumberDefault: —Start time from the server so the timer is accurate; if omitted, it counts from the switch to running.
durationnumber (ms)Default: —The actual duration once finished.
Trace.startedAt / durationnumberDefault: —Overall timer while running; total duration when finished.
Trace.announceChangesbooleanDefault: trueChanges in the same tick are merged into one polite announcement.

When something fails

Press Run: the get_order step times out on the first run. The steps after it become Cancelled, and the header offers the next action.

Assistant: check order #A1023

Pending

  • Analyze the question, Pending
  • search_docs("returns"), Pending
  • get_order("#A1023"), Pending
  • Write the answer, Pending
  • Describe the error in the user's language (“did not respond within 5 seconds”); the technical log goes in details.
  • Mark dependent steps as cancelled. Don't leave them pending as if they will still run.
  • When possible, retry from the failed step instead of running everything again.

Accessibility

AspectHow Trace handles it
SemanticsA named <section> (from the title); steps are role=list / listitem, child steps are a nested list.
StateEach step has hidden text such as “, Hoàn tất” (Done) or “, Lỗi” (Error) in its name, so it does not rely on the icon or color.
ExpandingThe title of a step with details / child steps is a disclosure button with aria-expanded + aria-controls; Enter / Space opens it.
AnnouncementsOne polite role=status region reads “Step name: state”; changes in the same tick are merged into one.
MotionThe spinner stays still and new steps don't fade in when the user turns on reduced motion.
LogTraceLog is always dir="ltr" and focusable with Tab so it can be scrolled with the keyboard.

Make the TraceStep title a string (or pass announceLabel) so the state announcement has content.

Do and don't

Do
  • Show a step as soon as the agent starts it, not after it finishes.
  • Record a duration for each step so users can see where the time goes.
  • Open the failed step by default, with the cause and a retry action.
  • Use a small, collapsed Trace in the AI assistant's messages.
Don't
  • Faking progress with made-up steps. Only show what actually happens.
  • Leaving dependent steps pending after the pipeline has stopped.
  • Putting sensitive data (tokens, passwords) in visible metadata or logs.