DEV Community

Haley
Haley

Posted on

Show the Page State Before Approving an Agent Click

This is a composite scene from design reviews. I almost approved a click I could not see. The agent wanted Confirm on a live refund form. I had no picture of the page the person actually faced.

Who owned that decision in that second? I did, as the human still holding the gate. Money would leave an account after one click. The only reversible moment sat before that click, not after.

Browser agents make this feel ordinary now. A model reads a page, then asks to act. Designers still treat the preview as proof. Is a tidy button enough evidence for irreversible action?

I do not think so. A preview is a hypothesis wearing a screenshot costume. The missing piece is the visible page state at decision time. Without that state, approval is theater.

I am proposing a from-zero tutorial, not a completed study. Treat every protocol below as unexecuted until you run it. I will still write it as a working path you can verify stage by stage.

Name the decision before you name a model

Start with the human choice, not the stack. Write one sentence that names owner, consequence, and reversibility. Mine reads like this, on purpose, in plain language.

Decision: approve or refuse an in-page agent click.
Owner: the on-call designer or reviewer.
Consequence: a refund submits on the user's behalf.
Reversible until: the click is sent to the page.
Stop if: visible page state is missing, stale, or unmatched.
Enter fullscreen mode Exit fullscreen mode

Does that sentence still work if the model is free and noisy? It must. Cheap generation does not cheapen the click. It only makes confident copy easier to mint.

When I need a scratch space for that sentence, I draft on MonkeyCode. Disclosure: This article was prepared as part of MonkeyCode's product outreach. The project is open source, with free model access and a free server option. I use that pair to generate candidate review cards. I do not use it as evidence that a click is safe.

Verify this stage before you go on. The card must name a person, not a model. If the owner field says "the AI," stop. Rewrite until a human can refuse.

grep -E "Owner:|Reversible until:|Stop if:" decision.txt
test -s decision.txt && echo "stage0: decision file is not empty"
Enter fullscreen mode Exit fullscreen mode

If grep returns nothing, you do not have a decision. You have a vibe. Do not proceed.

Capture the visible page state, not the model's summary

The agent will offer a summary. Summaries are polite. They also hide the broken checkbox, the unread fee, and the already-selected annual plan. I want the state the user could have seen.

Think of it like a night-shift nurse. You do not accept "patient is fine." You want the last charted numbers. An agent click is a medical order. Chart first.

Here is the proposed capture object. Label it proposed. I have not shipped this schema to production.

{
  "review_id": "refund-click-017",
  "action": "click",
  "target_role": "button",
  "target_name": "Confirm refund",
  "page_url": "https://example.local/refund",
  "viewport": "1280x720",
  "visible_text_excerpt": "Refund $48.00 to Visa ending 4412. This cannot be undone.",
  "visible_warnings": ["This cannot be undone."],
  "dom_digest": "sha256:pending",
  "captured_at": "2026-09-23T15:04:11Z",
  "stale_after_seconds": 20,
  "model_summary": "User wants to confirm the refund."
}
Enter fullscreen mode Exit fullscreen mode

Notice the last field. I keep the model summary. I refuse to approve from it. Which field should stop approval if blank? visible_text_excerpt and visible_warnings. Extra chrome like model confidence only adds noise.

Verify the capture before drafting copy. A tiny check is enough.

node -e "const fs=require('fs'); const c=JSON.parse(fs.readFileSync('page-state.json','utf8')); if(!c.visible_text_excerpt || !c.captured_at) process.exit(1); if(c.model_summary && !c.visible_warnings) process.exit(2); console.log('stage1: page state has excerpt and timestamp');"
Enter fullscreen mode Exit fullscreen mode

Exit code two means you trusted a summary without warnings. That is the trap. Stop there.

Draft the review card from evidence, then from a free model

Now you may ask a free model to help. Ask it to format the card. Do not ask it to decide. The server is a desk, not a judge.

I paste the capture object. I ask for three fields only: what_the_user_can_see, what_the_click_will_do, and what_would_make_me_refuse. If the draft invents a fee you did not capture, throw the draft away.

You are formatting a review card, not approving a click.
Use only the JSON I paste.
If a fact is missing, write MISSING, never guess.
Return markdown with those three fields, nothing else.
Enter fullscreen mode Exit fullscreen mode

That prompt is a fence. Free models hop fences when you praise them. Do not praise them here. Verify the draft against the capture, word by word.

node -e "const fs=require('fs'); const card=fs.readFileSync('review-card.md','utf8'); const state=JSON.parse(fs.readFileSync('page-state.json','utf8')); if(!card.includes(state.visible_text_excerpt.slice(0,24))) process.exit(1); if(/\\$[0-9]/.test(card) && !state.visible_text_excerpt.includes('$')) process.exit(2); console.log('stage2: card quotes visible money, invents none');"
Enter fullscreen mode Exit fullscreen mode

Did the card mention a dollar the page never showed? Kill it. Generated helpfulness is still a lie.

Research the approval with stop conditions

I do not run a vibe check with five friends. I run four scenarios that try to make a reasonable person approve too early. This is a protocol, not a finding. Say that out loud to your team.

Scenario A: the excerpt is stale by thirty seconds. Scenario B: the warning sits below the fold. Scenario C: the target name is Confirm, the visible text says trial convert. Scenario D: the digest changes after the card renders.

Success is ugly and specific. Reviewers refuse A, C, and D every time. They pause B and scroll before any approve control enables. If anyone approves A, the pattern failed. You do not ship a nicer tooltip. You stop.

// proposed/stop-conditions.test.js — unexecuted example
const stops = {
  missingExcerpt: (c) => !c.visible_text_excerpt,
  stale: (c, now) => (now - Date.parse(c.captured_at)) / 1000 > c.stale_after_seconds,
  nameMismatch: (c) => !c.visible_text_excerpt.toLowerCase().includes("refund") && /refund/i.test(c.target_name),
  digestDrift: (before, after) => before.dom_digest !== after.dom_digest
};

function shouldEnableApprove(c, now, liveDigest) {
  if (stops.missingExcerpt(c) || stops.stale(c, now) || stops.nameMismatch(c)) return false;
  if (stops.digestDrift(c, { dom_digest: liveDigest })) return false;
  return true;
}
Enter fullscreen mode Exit fullscreen mode

Verify this stage with the four scenarios, not with a demo smile. Record refuses. Record the exact missing field. If a reviewer says "I just trusted the summary," that is a stop, not a coaching note.

node proposed/stop-conditions.test.js || echo "stage3: stop conditions are not green yet"
Enter fullscreen mode Exit fullscreen mode

Check accessibility at the pattern, not the palette

An approve button that appears beside missing evidence is an accessibility bug. Keyboard users will hit it because it is the next control. Screen reader users will hear "Approve" without the excerpt. That is not inclusive. That is a trap with good contrast.

I want the approve control disabled until the excerpt is in the same reading order. The warning must be announced before the button name. Focus must land on the missing-evidence text when a stop fires. Can a person who cannot see the screenshot still refuse for the same reason I refuse?

If the answer is no, you designed a visual ritual. You did not design a decision. Fix the reading order before you polish motion.

<!-- proposed review region, not production markup -->
<section aria-labelledby="review-title">
  <h2 id="review-title">Review this click</h2>
  <p id="excerpt">Refund $48.00 to Visa ending 4412. This cannot be undone.</p>
  <p role="status" id="stop">Approve is unavailable. Visible page state is stale.</p>
  <button type="button" aria-describedby="excerpt stop" disabled>Approve click</button>
</section>
Enter fullscreen mode Exit fullscreen mode

Verify with a keyboard-only pass and a screen reader pass. If Approve is reachable while disabled text is unread, fail the stage. Do not argue about icons.

Rehearse hand-back before you need it

Approval is not the end of the flow. Refusal is a product path. When the digest drifts, the agent must hand the page back with the last good excerpt still visible. The person should not hunt through a chat scroll for the warning they already earned.

The recovery copy has to quote the stop, not soothe it. "I cannot click Confirm. The form changed after I read it." That sentence is a brake. "Let me try again" is a shove. Which one belongs at the reversible moment?

Hand-back record
Stop: digestDrift
Last visible excerpt: Refund $48.00...
User-facing line: I cannot click Confirm. The form changed after I read it.
Next human action: recapture page state, or take the click yourself.
Discarded line: Looks good, submitting now.
Enter fullscreen mode Exit fullscreen mode

Keep the discarded line in the record. Future you will try to reuse it. Future you is tired. The record is the adult in the room.

Verify recovery by forcing a drift on purpose. Change one visible price. Confirm Approve stays dark. Confirm the status text matches the stop name. If the agent still says submitting, you failed recovery, not copywriting.

# proposed drift rehearsal — run against a local fixture only
cp page-state.json page-state.before.json
sed -i.bak 's/48.00/72.00/' page-state.json
node -e "const a=require('./page-state.before.json'); const b=require('./page-state.json'); if(a.visible_text_excerpt===b.visible_text_excerpt) process.exit(1); console.log('stage5: drift detected, approve must stay blocked');"
Enter fullscreen mode Exit fullscreen mode

What this is not

This walkthrough is not a benchmark of free models. It is not a claim that in-browser agents are ready. It is not permission to skip legal review on refunds. I have no participant counts to flash at you. I have a decision mechanism you can test on Tuesday.

Do not use this if you cannot recapture page state. Do not use it if no human owns refusal. Do not use it to launder a click through a pretty card. Designers who need a model to invent the fee should close the laptop.

The analogy I keep using is a signed check. The model can fill the memo line. The page state is the amount. You still hold the pen. Would you sign a check because the memo sounded confident?

Ask one last pair of questions before you enable Approve. Which missing evidence should stop the click today? Which extra token of model self-praise would only add noise? If your team cannot answer both, you are not researching trust. You are decorating a guess.

Top comments (0)