DEV Community

babycat
babycat

Posted on

Build an Accessible Agent Task Timeline, Not a Spinner

A spinner is honest for about two seconds. After that, it stops answering the questions a person actually has:

  • What is happening now?
  • Did anything finish?
  • Is the product waiting for me?
  • Can I leave and come back?
  • What will cancellation preserve?

Long-running agent tasks need two interface layers: a concise current-state summary and a durable event history. The summary helps someone decide what to do now. The history helps them understand what already happened without relying on animation or memory.

Start with a state-to-message contract

Do not let each backend event become raw UI copy.

State Summary Primary action
queued Waiting for an environment Cancel
running Name the current verifiable step View details or cancel
needs input State the decision and consequence Answer question
succeeded Name the artifacts ready for review Review patch/results
failed State what is preserved and what can retry Retry or inspect logs
canceled State whether partial changes remain Review or discard

“Working…” is not a useful running message. “Running unit tests: 42 of 68 complete” is useful because it has an object and progress that the system can actually verify.

A single-file implementation

Save the following as task-timeline.html and open it in a browser:

<!doctype html>
<html lang="en">
  <meta charset="utf-8" />
  <meta name="viewport" content="width=device-width, initial-scale=1" />
  <title>Accessible task timeline</title>
  <style>
    body { font: 1rem/1.5 system-ui; max-width: 44rem; margin: 3rem auto; padding: 0 1rem; }
    .summary { padding: 1rem; border: 2px solid #555; border-radius: .75rem; }
    li { margin-block: .75rem; }
    time { color: #555; }
    button { padding: .6rem .9rem; margin-right: .5rem; }
  </style>
  <main>
    <h1>Refactor authentication</h1>
    <p id="summary" class="summary" role="status" aria-live="polite" aria-atomic="true">
      Task queued. No action needed.
    </p>
    <ol id="events" aria-label="Task event history"></ol>
    <button id="advance" type="button">Simulate next update</button>
    <button id="cancel" type="button">Cancel task</button>
  </main>
  <script>
    const updates = [
      ["running", "Environment ready; analyzing the repository."],
      ["needs input", "Choose the target branch before changes continue."],
      ["running", "Branch selected; running tests."],
      ["succeeded", "Task complete; review the patch and test results."],
    ];
    let index = 0;
    const summary = document.querySelector("#summary");
    const events = document.querySelector("#events");
    const advance = document.querySelector("#advance");
    const cancel = document.querySelector("#cancel");

    function addEvent(state, message) {
      summary.textContent = `${state}: ${message}`;
      const item = document.createElement("li");
      const now = new Date();
      item.innerHTML = `<strong></strong><br><span></span> <time></time>`;
      item.querySelector("strong").textContent = state;
      item.querySelector("span").textContent = message;
      item.querySelector("time").textContent = now.toLocaleTimeString();
      item.querySelector("time").dateTime = now.toISOString();
      events.append(item);
    }

    advance.addEventListener("click", () => {
      if (index >= updates.length) return;
      addEvent(...updates[index++]);
      if (index === updates.length) {
        advance.disabled = true;
        cancel.disabled = true;
      }
    });

    cancel.addEventListener("click", () => {
      addEvent("canceled", "Task canceled; partial changes require review.");
      advance.disabled = true;
      cancel.disabled = true;
    });
  </script>
</html>
Enter fullscreen mode Exit fullscreen mode

The demo deliberately keeps focus on the button the user activated. Moving focus to every new event would make a live interface difficult for keyboard and screen-reader users. The role="status" summary announces a short, polite update; the ordered list remains available for deliberate review.

Accessibility decisions hiding in the code

One live region, not a live log

Marking the whole event stream as live can produce a wall of announcements. Announce the newest decision-relevant summary once. Let users navigate history normally.

Text carries the state

Color can reinforce success or failure, but it cannot be the only signal. The demo uses visible state words and complete messages. Add icons only with accessible names or hide decorative icons from assistive technology.

Cancellation has a result

The button says “Cancel task,” but the resulting message says what remains: partial changes require review. In a real product, confirm whether cancellation stops future work, discards a workspace, preserves a patch, or merely requests termination.

Time is machine-readable

The visible time is local and readable. The dateTime value preserves an ISO timestamp for assistive technology and future formatting.

A documented product example

The public MonkeyCode repository includes this screenshot of its AI task workspace:

MonkeyCode AI task workspace showing a development task interface

Source: MonkeyCode repository at the reviewed commit, licensed with the project under AGPL-3.0.

The screenshot is useful context for a frontend review, but it is not an accessibility test. A serious review still needs keyboard navigation, focus behavior, semantic inspection, zoom and reflow, contrast, screen-reader announcements, reduced motion, error recovery, and tests with people who use assistive technology.

For a task workspace such as MonkeyCode, I would verify these flows first:

  • returning to a task after the page reloads;
  • receiving a question while focus is elsewhere;
  • canceling while a command is running;
  • distinguishing “cancel requested” from “canceled”;
  • reviewing partial files after a failure;
  • opening a preview without losing task context.

This article demonstrates an independent UI pattern. It does not claim that MonkeyCode currently implements the exact markup or behavior above.

Disclosure: I contribute to the MonkeyCode project. Product observations are limited to the linked public screenshot and repository documentation.

Frontend developers can join the MonkeyCode Discord to discuss the task interface with the team. Readers interested in trying the hosted workflow can also ask about current free model-credit availability, eligibility, and usage limits.

The core pattern is simple: keep the current state short, keep the history durable, announce only what needs attention, and make every stop state explain what the user can recover next.

Top comments (0)