# How 99 Tracks and Displays In-Flight Request Status with the Throbber

> Discover how 99 tracks in-flight request status using a self-scheduling Throbber object. Learn how it polls for active requests and animates a Unicode spinner for clear visualization.

- Repository: [ThePrimeagen/99](https://github.com/theprimeagen/99)
- Tags: internals
- Published: 2026-02-16

---

**99 uses a self-scheduling Throbber object that polls the internal `_99_state.__request_by_id` registry every 100 milliseconds to animate a Unicode spinner and render a floating window listing all active requests marked with `status = "requesting"`.**

The 99 plugin for Neovim provides real-time visibility into asynchronous operations through its **in-flight request throbber**. When you enable `show_in_flight_requests` in your setup configuration, 99 creates a lightweight status window that updates continuously to show exactly which requests are currently executing. This article examines the internal machinery that makes this possible, from state bookkeeping to the animation loop that drives the visual spinner.

## State Bookkeeping for Active Requests

99 maintains a centralized registry of all active operations inside `_99_state.__request_by_id`. Each entry in this table represents a single request context containing a `status` field. While a request is executing, its status is set to `"requesting"`.

The state module exposes an `active_request_count()` method that iterates over `__request_by_id` and returns the number of entries currently marked as in-flight. This count drives the numeric display in the throbber window.

*Source:* [`lua/99/init.lua`](https://github.com/ThePrimeagen/99/blob/main/lua/99/init.lua) (lines 476-500) – request loop and state inspection.

## Throbber Lifecycle and Animation Logic

The visual heartbeat of the feature is the `Throbber` class defined in [`lua/99/ops/throbber.lua`](https://github.com/ThePrimeagen/99/blob/main/lua/99/ops/throbber.lua). This object manages a self-scheduling animation loop that updates the UI without blocking Neovim's main thread.

### Creating the Throbber Instance

You instantiate a throbber with `Throbber.new(callback, opts)`. The `callback` receives a Unicode icon string on every tick. The `opts` table accepts `throb_time` (duration of the spinning phase) and `cooldown_time` (pause between cycles).

*Source:* [`lua/99/ops/throbber.lua`](https://github.com/ThePrimeagen/99/blob/main/lua/99/ops/throbber.lua) (lines 15-31) – constructor and initialization.

### The Animation Loop and Timing

The internal `_run` routine computes a progress percentage based on elapsed time and selects an icon from a randomly chosen icon set. The throbber alternates between an active "throbbing" phase (spinning) and a short cooldown pause.

Scheduling relies on `vim.defer_fn` with a **100 ms tick interval**, ensuring the spinner updates ten times per second without manual timer management.

*Source:* [`lua/99/ops/throbber.lua`](https://github.com/ThePrimeagen/99/blob/main/lua/99/ops/throbber.lua) (lines 95-115) – run loop and timer scheduling.

## Rendering the In-Flight Status Window

When `show_in_flight_requests` is enabled, 99 creates a dedicated status window. The throbber's callback function assembles the content displayed inside this window.

The callback constructs an array of lines:

- **Line 1:** The current spinner icon, the total count of in-flight requests (from `active_request_count()`), and the icon repeated for visual balance.
- **Subsequent lines:** The `operation` string of each request whose `status == "requesting"`, providing human-readable context for what is currently executing.

After assembling the lines, the callback resizes the floating window and writes the content to the buffer using `vim.api.nvim_buf_set_lines`.

*Source:* [`lua/99/init.lua`](https://github.com/ThePrimeagen/99/blob/main/lua/99/init.lua) (lines 505-531) – window creation and callback implementation.

## Resource Cleanup and Window Teardown

To prevent resource leaks, 99 implements `shut_down_in_flight_requests_window`. This helper triggers when the active request count reaches zero or when the window becomes invalid.

The cleanup routine calls `throb:stop()` to halt the deferred timer, then closes the floating window and nils the buffer reference. This ensures the 100 ms animation loop terminates and memory is reclaimed.

*Source:* [`lua/99/init.lua`](https://github.com/ThePrimeagen/99/blob/main/lua/99/init.lua) (lines 776-786) – cleanup logic and throbber shutdown.

## Configuration and Usage Examples

Enable the in-flight request throbber in your Neovim configuration by setting `show_in_flight_requests` to `true` during setup.

```lua
-- Enable the in-flight request throbber in your Neovim config
require("99").setup{
  show_in_flight_requests = true,   -- turn the throbber on
  -- other 99 options …
}

```

When you initiate a request, it automatically appears in the throbber window.

```lua
-- Manually start a request and see it appear in the throbber window
local request = require("99.request").new(function(ctx)
  ctx:run("git status")   -- the operation string shown by the throbber
end)

request:run()   -- while this runs, the throbber window updates

```

For advanced use cases, you can instantiate a standalone throbber to drive custom UI elements.

```lua
-- Custom throbber example (rarely needed)
local Throbber = require("99.ops.throbber")
local my_throb = Throbber.new(function(icon)
  -- Write the icon somewhere custom, e.g. statusline
  vim.api.nvim_set_var("my_throb_icon", icon)
end, { throb_time = 800, cooldown_time = 150 })

my_throb:start()
-- … later …
my_throb:stop()

```

## Summary

- **State tracking:** 99 stores active requests in `_99_state.__request_by_id` with a `status` field set to `"requesting"` during execution, exposing `active_request_count()` for UI updates.
- **Animation engine:** The `Throbber` class in [`lua/99/ops/throbber.lua`](https://github.com/ThePrimeagen/99/blob/main/lua/99/ops/throbber.lua) uses `vim.defer_fn` with a 100 ms interval to cycle through Unicode icons while alternating between throbbing and cooldown phases.
- **Window rendering:** The throbber callback assembles lines showing the spinner icon, total request count, and individual operation strings, then writes them to a floating buffer via `vim.api.nvim_buf_set_lines`.
- **Resource management:** `shut_down_in_flight_requests_window` stops the throbber timer and closes the window when requests complete, preventing memory leaks and orphaned timers.

## Frequently Asked Questions

### How does the throbber impact Neovim's performance?

The throbber uses `vim.defer_fn` with a **100 millisecond tick interval**, which is sufficiently coarse to avoid consuming significant CPU cycles. Because the callback only updates a small floating window and iterates over the active request table, the overhead is negligible even with multiple concurrent requests.

### Can I customize the throbber icons or animation timing?

Yes. When creating a throbber via `Throbber.new(callback, opts)`, you can pass an options table specifying `throb_time` (duration of the spinning phase in milliseconds) and `cooldown_time` (pause duration). While the built-in throbber uses predefined Unicode icon sets selected randomly, you can implement a custom callback to render any character or symbol you prefer.

### What is the difference between the throbber and request_status.lua?

The **throbber** ([`lua/99/ops/throbber.lua`](https://github.com/ThePrimeagen/99/blob/main/lua/99/ops/throbber.lua)) manages a global floating window that aggregates all in-flight requests and displays a spinning animation alongside the total count. In contrast, **[`request_status.lua`](https://github.com/ThePrimeagen/99/blob/main/request_status.lua)** provides localized virtual text spinners that attach to individual buffer lines, showing the status of a specific request directly in the editor margin. The throbber is ideal for a dashboard overview, while request_status.lua offers granular, inline feedback.

### How do I disable the in-flight request window after enabling it?

Set `show_in_flight_requests = false` in your `require("99").setup()` call and restart Neovim. If you need to disable it programmatically at runtime, you can call the internal cleanup helper `shut_down_in_flight_requests_window()`, which stops the throbber timer and closes the floating window immediately.