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>
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:
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)