Outbound validator + refine loop

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).

Source: the FlatsBratislava operator manual.