I2.06.2cancel must abort not hidedesign

Cancel must actually abort, not merely hide

Aliases: fake cancel · abort not dismiss · in-flight after dismiss

What it is

After cancel, wait chrome can vanish while the request is still in flight and the server is still writing. That is fake cancel: the UI left the wait, the work did not abort. The person’s model is “this stopped”; the system’s is “the display stopped”. Once those fork, everything after is a ghost — a second submit colliding with the still-running first, a payment they thought they cancelled, a result they cancelled coming back. Cancel’s meaning is abort: stop the transfer, stop the processing, stop the side effects — not merely dismiss.

Why it happens

The wait UI is the only observation hole onto this job. Closing the hole does not close the job. An HTTP request dropped on the floor, a WebSocket still pushing, a server job already queued — typical paths where the job lives on after the hole closes. If the client only setState(idle) and never abort()s, even the local side has not stopped. People plan the next move as if it had: tap again, change parameters, leave. The living job still writes under the old parameters, so you get double writes, writes into a context already left, or completion while the person has started the opposite action.

A true abort has to pass through three layers: the UI is no longer waiting, the client no longer handles the response, the other side no longer continues this work. Miss one layer and fake cancel leaks from it. If the third layer cannot be done, pretending the first two equal cancel is a lie — change the wording to “stop showing”, and point at a still-queryable task. That is already an admission that abort failed, not a fulfilment of cancel.

Where it stops holding

A read-only fetch that the other side does not stop can sometimes be tolerated (one wasted compute), but the response must still be dropped: it must not land in the UI or the cache after cancel, or people will see content that “was cancelled and then appeared”. Writes, payments, deletes: the other side not stopping is an incident, and dropping the response does not patch it. The abort signal to the other side can itself fail, so idempotency is required: if abort arrives late, running the same work again should be a no-op. When local work is already done and only the last display frame remains, abort may lose the race to completion; treat that as completed, do not show a successful cancel after completion.

Applying it

  • On the cancel path, together: take down the wait, abort the client request, tell the other side to stop. Writes must wait for the other side to confirm stop, or explicitly degrade to “display stopped; the job may still be running”.
  • Drop every response that arrives after cancel. Do not write it into UI or cache.
  • If the other side cannot be aborted, do not call the button Cancel. Rephrase and give a place to query the task.
  • How to check: cancel, then immediately inspect the request on the server or proxy. If it is still writing, or later pushes a result into the UI, the cancel was fake. Tapping the same action again must not become two submits.

Related

  • Same group: I2.06.1 A long wait must be cancellable · I2.06.3 After cancel, say what happens to work already done
  • Nearby: I1.06 Timeout strategy · I3.05 Idempotency and duplicate submit · I3.12 Background and long-running tasks
  • Search terms: abort vs dismiss · zombie request · true cancel

Cards in the same group

Quick Actions

Share

Share this page

ios_share

https://hci.top/en/handbook/I2.06.2