Adaptive Card Optimistic UI for Long-Running Actions Prompt
Design an Adaptive Card pattern that shows immediate optimistic feedback (disabled buttons, in-progress state, fallback timeout copy) while a slow backend action runs, so responders never double-click a deploy or approval.
- Target user
- Bot engineers building ChatOps cards that trigger slow backend operations
- Difficulty
- Intermediate
- Tools
- Claude, ChatGPT
The prompt
You are a senior platform engineer who has shipped Teams ChatOps cards where a single "Run" click kicks off a 30-second pipeline, and you have watched responders double-click because nothing visibly changed. I will provide: - The card's actions and the backend operation each triggers: [CARD ACTIONS AND BACKEND CALLS] - Typical and worst-case backend latency: [P50 AND P99 LATENCY] - Whether the card uses Action.Execute (Universal Action Model) or Action.Submit: [ACTION TYPE] - Idempotency guarantees of the backend (can a duplicate request be safely deduped?): [IDEMPOTENCY STATUS] Your job: 1. **Immediate acknowledgement** — for Action.Execute, return an InvokeResponse with a refreshed card within the synchronous window that disables the triggering action and shows an "in progress" state; for Action.Submit, update the original activity by ID right after receiving the message. 2. **Disable-on-click** — render the post-click card with the action removed or replaced by a non-interactive TextBlock so a second click is impossible, not just discouraged. 3. **Optimistic vs confirmed state** — distinguish "request accepted" (optimistic, shown instantly) from "operation completed" (confirmed, shown after a proactive card update when the backend finishes). Label each clearly. 4. **Timeout & failure copy** — define what the card shows if the backend exceeds [P99 LATENCY]: an honest "still running, check [DEEP LINK]" message rather than a silent stall. 5. **Idempotency key** — include a client-generated correlation/idempotency key in the action data so a redelivered invoke or a retried request is deduped server-side. 6. **Refresh.userIds** — if multiple responders see the card, use the refresh model so each viewer gets the current state instead of a stale snapshot. Output as: (a) the in-progress card JSON, (b) the confirmed/failed card JSON variants, (c) the InvokeResponse handler pseudocode with the idempotency key flow, (d) a timeout-handling decision table. Bias toward: disabling the action over trusting users not to double-click, and honest timeout copy over a frozen card.
Why this prompt works
ChatOps cards live in a strange gap between a button and an API call. A user clicks “Run rollback,” but the rollback takes twenty or thirty seconds, and during that window the card looks exactly as it did before the click. Human nature does the rest: people click again, and again, and now you have three rollbacks queued. This prompt forces the model to treat that latency window as a first-class design problem rather than an afterthought, separating the optimistic acknowledgement (shown instantly) from the confirmed result (shown when the backend actually finishes).
The instruction set deliberately couples UI state with backend safety. It is not enough to gray out a button — a redelivered invoke activity or a network retry can still hit your backend twice, so the prompt insists on a client-generated idempotency key threaded through the action data. By asking for the in-progress, confirmed, and failed card variants as distinct JSON, plus a timeout decision table, the output becomes something you can paste straight into a handler instead of a vague description of “showing a spinner.”
Finally, anchoring the request in P50 and P99 latency keeps the design honest about the worst case. The model has to answer “what does the responder see if this hangs for ninety seconds?” — and the answer is a deep link to authoritative status, not a frozen card. That is the difference between a card people trust under pressure and one they spam-click during an incident.