Lying spinners, stale labels, one-way doors
UX rules from a repo whose users stare at it all day and notice every lie the UI tells.
- Problem
Spinners that never stop, “connected” badges that aren't, and actions you can't undo teach users to distrust the UI.
- Idea
Treat every label as a promise: separate “connected” from “fresh”, never show cache as live, and give every action a way back.
One line from this repo's guide stuck with me:
Our users drive agents all day and notice a dropped frame, a lying spinner, and a stale label.
Underneath the UX rules is a state-modelling rule: a status label is a claim about the system, so the state behind it has to be precise enough to back it up.
Try it: naive vs honest status
open or close the socket, receive the config, let the data age
A naive badge says "Connected" as soon as a socket opens. An honest one tracks connection and freshness separately: "Connecting…" until the server's initial config arrives, and "cached" whenever the data on screen isn't live.
Two states, not one
Connection health
A socket opening doesn't prove the server is usable. The client waits for the initial server config before it calls itself ready.
If a data subscription fails on a healthy socket, the UI must not say "reconnecting", because no reconnect is coming.
Data freshness
Cached data stays readable offline, but it must never imply a live connection, or overwrite newer live data during a reconnect.
Showing stale data is fine. Showing stale data as if it were live is the bug.
The server owns derived state
If three devices show the same item, none of them should work out its status from its own clock. A status like "this thread is finished and can be tucked away" is evaluated on the server and stored. Clients just render it, so every device agrees, even one that was asleep when the status changed.
No one-way doors
If you added a way in, add the way out and the way to see it. Snooze needs unsnooze. Close needs reopen. A one-way door is a bug.
In practice: settle and un-settle, snooze and wake, a five-second Undo after sidebar actions, and a shortcut that reopens the last closed tab.
Hit every surface
The most common defect in this repo is a change that works on the path you tested and is missing everywhere else.
- Entry pointsChat, settings, command palette, keybindings.
- Clients and modesWeb, desktop, mobile; local, remote and tunnel connections.
- Integrations and agentsEach backend integration, and AI agents reaching the feature through tools.
- The reverse and the docsThe way back out of every new state, and the docs that describe it.
Components built from slots
Their banner component exports small slots, each owning its styling, so a new banner is a few lines with no classes of its own:
<Banner.Root variant="info">
<Banner.Row>
<Banner.Icon />
<Banner.Content>Agent is waiting for approval</Banner.Content>
<Banner.Actions>…</Banner.Actions>
</Banner.Row>
</Banner.Root>
And no continuously repainting animations: a 120 Hz laptop user notices the fan before the shimmer.
Steal this
Every label is a promise. Model "connected" and "fresh" as separate states, never let cache pose as live data, give every action a reverse, and let the server decide derived status so devices agree.
Use it in your code
A prompt for your coding agent
Run it from the root of your repo. It checks whether the idea fits before it changes anything.
Audit this app's UI for status it can't back up.
The idea: every label is a promise. "Connected" and "up to date" are different states. Cached data must never look live. Every action needs a way back. Derived status should come from one source of truth, not from each client's own clock.
1. Find where the UI shows connection, loading, sync or freshness status, and check what each one is actually based on. A spinner that keeps spinning after an error, or "online" because a socket opened, are typical lies.
2. Find cached or offline data and check whether it can be shown as if it were live, or overwrite newer data on reconnect.
3. Find one-way doors: actions with no undo or reverse, like archive without unarchive or dismiss without restore.
4. Pick the most misleading case and fix it, with a test for the state that used to lie.
Report the rest as a short prioritized list. Keep the change small.Read the source
Where this comes from
T3 Code (pingdotgg/t3code), the open-source GUI for coding agents. The files behind this lesson: