Every engineer who has shipped an upload page has shipped this bar. Eight lines. A <div> for the track, a second <div> inside with width: ${value}% and a transition: width 0.3s ease-out. The reviewer opens the demo video: 0, 20, 40, 60, 80, 100. The bar fills. The reviewer nods. Merge. Ship.

A week later three tickets land. A user uploading a 4GB dataset reports “the bar jumped backwards.” A second reports “the bar slams to zero when I retry a chunk.” A third asks “does the upload page even have a progress bar? My screen reader is completely silent on it.” Same eight lines. Three user groups. Zero code changes.

odometer (naive)

25%

jump forward feels like landing. drop backward feels like a glitch. same 0.3s ease-out curve — reverse-played, it's ease-in.

The bar above is running the naive version. Drag the scrubber up and down — the bar follows. Try dragging it from 80 back to 40: the bar sweeps smoothly forward, then SNAPS backward on the retreat. That's ease-out playing in reverse; forward it reads as “landing”, backward it reads as “glitch”.

FIG. 3 — THE 8-LINE BAR EVERY JUNIOR SHIPS FIRST
1
// The 8-line version every junior ships first.
2
// Works when value only GROWS. Breaks the moment the signal reverses:
3
//   - File retry returns from 80% to 0% → bar slams backwards
4
//   - Batch recompute drops rolling average → looks like a glitch
5
//   - Screen reader hears nothing — no role, no valuenow, no label
6
function NaiveBar({ value }) {
7
  return (
8
    <div className="track">
9
      <div
10
        className="fill"
11
        style={{
12
          width: `${value}%`,
13
          transition: 'width 0.3s ease-out',
14
        }}
15
      />
16
    </div>
17
  );
18
}

The eight lines look harmless. Three probes below test whether they actually serve the user. Predict the outcome before each reveal — the probe answers are how the felt wall lands.

01 / 03 · The direction probe

You have a bar with transition: width 0.3s ease-out. The current value is 80. The server returns 60 after a retry handshake. The bar needs to settle at 60. Which motion does the user actually see?

With transition: width 0.3s ease-out and the value dropping from 80 to 60, how does the bar look?

direction
forward-feel
silent
residue