Refine button
When a draft is blocked by the outbound validator, the operator sees three options on the draft surface — V28 inbox, V28 Pulse task tile, mobile thread, legacy DueTodayTaskCard, mobile MobileTaskResolveSheet, task-drawer V28TaskRowActions, dashboard task-card.
The three buttons (gh#879):
| Button | What |
|---|---|
| Refine | Run one refinement pass: the validator's findings get fed back to the LLM, which rewrites the draft addressing them. Re-validate; if clean, send; if still blocked, return for another decision. |
| Send Anyway | Override the block + send the draft as-is. Audit row written with validatorVerdict=overridden (gh#832). Use when the validator is wrong. |
| Dismiss | Discard the draft + mark the task done without sending. Use when no send was needed in the first place. |
Where they appear
After gh#879 + the sibling-site sweep (gh#766, gh#769, gh#757, gh#800), the 3-button surface is consistent across:
- V28 Inbox composer (parent — operator's primary surface)
- V28 Pulse task tile (gh#769 — was raw-dump only)
- V28 Task drawer / V28TaskRowActions (gh#757 — was raw error)
- V28 Dashboard task-card (gh#800 — SEND ANYWAY didn't bypass validator before; now does)
- Legacy DueTodayTaskCard (gh#766)
- Mobile thread (gh#769)
- Mobile MobileTaskResolveSheet (gh#766)
Earlier each surface had its own (mostly broken) handling — raw JSON dumps, missing SEND ANYWAY, no Refine. Now all 8 surfaces use the same component.
The validation banner
Before the 3 buttons, the surface shows an operator-friendly summary of why the validator blocked:
⚠ This draft was blocked by Parking-info-relevance (MAJOR). The validator flagged: "the draft mentions parking but the guest never asked about parking."
Earlier (gh#753) the surface showed raw [slug]/MAJOR framing + raw JSON. After gh#753 the message is operator-friendly: human label + plain-English finding.
Refine result UX
When you tap Refine, the surface shows a VALIDATING indicator (gh#838) while the refine + re-validate runs. Either:
- ✓ Refined + cleared → composer re-populates with the refined draft → tap SEND to send it.
- ⚠ Refined but still blocked → surface returns with the new validator finding + the 3 buttons again.
Refine guards
A refine pass historically had two failure modes:
- gh#836 — refine prepended an exact copy of the prior host message + mixed languages. Fix: refine prompt strips that pattern; refine never re-includes the prior message verbatim.
- gh#799 — refine injected U+FFFC image placeholders into drafts for templates with no images. Fix: refine strips the image placeholder if the template doesn't carry images.
If you see the refined draft going wrong (copying prior message / placeholder garbage), that's a regression — file a bug report.
Implements: gh#879 (3-button surface on all 4 in-app block panels), gh#766 (legacy + mobile sibling sites), gh#769 (V28 Pulse + mobile + legacy raw-dump), gh#757 (task-drawer), gh#800 (dashboard SEND ANYWAY bypass), gh#838 (VALIDATING indicator), gh#753 (operator-friendly block message), gh#832 (override audit), gh#836 + gh#799 (refine corruption guards).