A Stop button for long-running processes (AI agents, exports, generation) that shows what you keep before you stop: on hover or focus, an anchored panel unfolds two trays, “You keep” (finished artifacts, bright, with their kind glyph) and “You lose” (in-progress items, dimmed, with their progress), plus a headline that states the salvage value (“You keep 7 of 9 items · 78% of the work”, weighted per item). Stopping is a press-and-hold: a ring fills, lost items crumble in proportion while kept ones lock one by one and slide into the button’s satchel; releasing early springs everything back. The list updates live while the process keeps producing, a two-step confirm mode is available, and the final state reads “Stopped · 7 items kept” with an “Open” action.
Label of the generated button (ignored when you provide your own child <button>, whose content is kept).
hold
number (ms)
1200
Hold duration required to stop (minimum 300).
mode
hold | confirm
hold
hold: press and hold (mouse, touch, Space); Enter twice still works from the keyboard. confirm: two clicks / two Enter presses, the second one plays the stop animation fast-forwarded.
unit
string
item|items
Artifact noun as “singular|plural” (without a bar, the plural adds “s”).
One artifact: ready (finished) or progress 0–1, weight (share in the salvage ratio, 1 by default), kind (doc | chart | table | image | video | audio | code | data | slides | file), meta (short trailing text: “6 pp.”, “PNG”), label (otherwise the element text). Any child element with data-salvage-item and the same attributes as data-* works too. Changes are tracked live.
Artifacts provided from JS (appended after declared children; reassign the array to update). When read: the consolidated list, with element for declared children.
summary
{ kept, lost, ratio, total }
What stopping now would keep / lose (read-only).
state
string
Current state (read-only): idle, armed, holding, rewinding, stopped.
Methods
Name
Description
open()
Opens the panel and keeps it open (Escape, an outside click or close() closes it).
close()
Closes the panel and cancels any hold or pending confirmation.
stop()
Stops immediately, without the hold (timeout, host decision…); emits mv-stop.
reset()
Back to the running state (a new run started): the list is live again.
Events
Name
Description
mv-stop-intent
The panel opens: the user is considering stopping. detail: { kept, lost, ratio }.
mv-stop
Stop confirmed. detail: { kept, lost, ratio } (the list is frozen at that moment).
mv-stop-cancel
Hold released early, confirmation abandoned or Escape. detail: { reason: "release" | "escape" | "blur" | "outside" | "timeout" | "close" }.
mv-salvage-open
Click on the final “Open” action. detail: { kept }.
Content structure
Name
Description
(children)
<mv-salvage-item> or [data-salvage-item] elements (data, never rendered as-is); an optional child <button> becomes the trigger.
CSS classes
Name
Description
mv-salvage-stop-trigger
The button: hold ring (-ring), label, satchel (-satchel) that fills as items lock.
Artifact row; [data-locked] once secured during the hold, [data-fresh] when it just finished.
mv-salvage-stop-action
Action shown after stopping.
CSS variables
Name
Default
Description
--mv-salvage-stop-color
var(--mv-danger)
Stop color: ring, fill, lost items.
--mv-salvage-stop-keep
var(--mv-success)
Color of what is kept: satchel, locks, meter.
Accessibility
A real <button> with aria-expanded / aria-controls pointing to the panel and aria-describedby pointing to a summary sentence kept up to date (“If you stop now, you keep 7 of 9 items (78% of the work); 2 in progress will be lost. Hold Space, or press Enter twice, to stop.”). The panel is a labelled non-modal region placed right after the button in the DOM, with two lists labelled by their titles (“You keep 7”, “You lose 2”); each row reads its state (“done”, “in progress, 42% done”, “queued”). Keyboard: keyboard focus opens the panel, holding Space stops, Enter then Enter confirms, Escape cancels at any point and closes; losing focus cancels a hold or pending confirmation. Touch: a long press is enough, a tap opens the panel and shows the hint, scrolling cancels. A role=status region announces the stop starting, the cancellation (“nothing was lost”) and the result; after stopping, focus moves to the “Open” action. Colors are always paired with an icon and a label (check / dashed crossed circle, “You keep” / “You lose”). The crumbling, the dust and the tokens flying into the satchel are aria-hidden. Reduced motion (OS setting or data-motion="reduce"): the hold gauge stays, crumbling becomes a plain fade, nothing flies, no spring. The animation loop runs only while holding and rewinding, never at rest; the panel closes when the button scrolls out of view.