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.
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"
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."
}
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');"
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.
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');"
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;
}
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"
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>
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.
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');"
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)