What kind of request is this
A new component
The problem
Agent apps need an approval step: the assistant proposes an action (run a command, send an email, delete a branch) and the conversation waits until the person approves or rejects it. flow_ui has nothing for this today. Hosts hand-roll the card through FlowCustomPart, each one re-solving the same three states, and stac_ai's tool loop has nothing to standardize on.
What you'd like
A FlowConfirmation card in the assistant turn: a host-written title ("Run flutter pub get?"), an optional description, an optional detail slot for any widget (a command preview, a diff, a FlowCodeBlock), and approve/reject buttons.
Three states, per the design file. State is data, as everywhere: the host passes the status in and re-renders. The card never flips itself.
| Status |
The card |
The buttons |
pending |
open, full ink |
both live |
approved |
quieted |
collapse to a record: check + label |
rejected |
quieted |
collapse to a record: cross + label |
The settled states matter as much as the pending one. The transcript keeps a scannable record of what was approved and what was refused.
API sketch
FlowConfirmation(
title: 'Run flutter pub get?',
description: 'The agent wants to fetch dependencies.',
detail: FlowCodeBlock(code: 'flutter pub get', language: 'bash'),
status: FlowConfirmationStatus.pending,
approveLabel: 'Approve', // host-localized, as always
rejectLabel: 'Reject',
onApprove: approve, // intent out; deciding is the host's move
onReject: reject,
padding: ..., // metric overrides, as everywhere
borderRadius: ...,
style: FlowConfirmationStyle(...), // over FlowTheme.confirmationStyle
)
FlowConfirmationStyle (proposed fields)
Follows the component-styles convention (#21): lives in lib/src/styles/, app-wide default on FlowTheme.confirmationStyle, widget wins over theme field by field, tokens under both.
backgroundColor: card fill. Defaults to the error card's 2% ink wash.
borderColor: card hairline. Defaults to outlineVariant.
titleStyle / descriptionStyle: merged over their role defaults.
approveColor: the approve accent. Defaults to primary.
rejectColor: the reject ink. Defaults to error.
recordColor: the settled check/cross and label. Defaults to onSurfaceVariant; the approved check keeps approveColor.
One new shape here: two opposed actions means two accent fields. First style class where that happens, so it should be a deliberate call.
Decisions to settle
- Ship a
FlowConfirmationPart too, rendered by FlowMessage with intent threaded through FlowThread. Same precedent as FlowErrorPart: the confirmation lives in message data, survives restore, and reaches the thread without messageBuilder.
- Settled cards keep the detail. Only the button row collapses into the record; the evidence of what was approved should not vanish with the decision.
- Pending is inert while the turn is still streaming. Nobody should approve an action whose description is still being written. Same reasoning as the code block hiding copy mid-stream.
- Expiry, timeouts, and "someone else decided" are host state. The host just passes a settled status.
Accessibility
The card announces on arrival (live region, like FlowErrorState; a permission request arrives unprompted). Labels double as the buttons' accessible names. The settled record reads as text, not disabled buttons.
Definition of done
What you're doing instead
A custom FlowCustomPart with a host-built widget. Works, but every host redoes the states, the record, and the accessibility story. For reference: assistant-ui ships this as its tool approval UI, and Claude and Cursor both converge on the same pending/approved/rejected card.
What kind of request is this
A new component
The problem
Agent apps need an approval step: the assistant proposes an action (run a command, send an email, delete a branch) and the conversation waits until the person approves or rejects it. flow_ui has nothing for this today. Hosts hand-roll the card through
FlowCustomPart, each one re-solving the same three states, and stac_ai's tool loop has nothing to standardize on.What you'd like
A
FlowConfirmationcard in the assistant turn: a host-written title ("Runflutter pub get?"), an optional description, an optionaldetailslot for any widget (a command preview, a diff, aFlowCodeBlock), and approve/reject buttons.Three states, per the design file. State is data, as everywhere: the host passes the status in and re-renders. The card never flips itself.
pendingapprovedrejectedThe settled states matter as much as the pending one. The transcript keeps a scannable record of what was approved and what was refused.
API sketch
FlowConfirmationStyle (proposed fields)
Follows the component-styles convention (#21): lives in
lib/src/styles/, app-wide default onFlowTheme.confirmationStyle, widget wins over theme field by field, tokens under both.backgroundColor: card fill. Defaults to the error card's 2% ink wash.borderColor: card hairline. Defaults tooutlineVariant.titleStyle/descriptionStyle: merged over their role defaults.approveColor: the approve accent. Defaults toprimary.rejectColor: the reject ink. Defaults toerror.recordColor: the settled check/cross and label. Defaults toonSurfaceVariant; the approved check keepsapproveColor.One new shape here: two opposed actions means two accent fields. First style class where that happens, so it should be a deliberate call.
Decisions to settle
FlowConfirmationParttoo, rendered byFlowMessagewith intent threaded throughFlowThread. Same precedent asFlowErrorPart: the confirmation lives in message data, survives restore, and reaches the thread withoutmessageBuilder.Accessibility
The card announces on arrival (live region, like
FlowErrorState; a permission request arrives unprompted). Labels double as the buttons' accessible names. The settled record reads as text, not disabled buttons.Definition of done
FlowConfirmation+FlowConfirmationStatus, exported from the barrelFlowConfirmationPart+FlowMessage/FlowThreadwiring (decision 1)FlowConfirmationStyleinlib/src/styles/+FlowTheme.confirmationStyle(feat: component styles on every widget with app-wide FlowTheme defaults #21 convention)padding/borderRadiusoverridesRestylingsection; README row; roadmap flipped; CHANGELOG entryWhat you're doing instead
A custom
FlowCustomPartwith a host-built widget. Works, but every host redoes the states, the record, and the accessibility story. For reference: assistant-ui ships this as its tool approval UI, and Claude and Cursor both converge on the same pending/approved/rejected card.