Build image / build-and-push (push) Successful in 9s
Co-authored-by: Steve Dudenhoeffer <[email protected]>
52 lines
1.5 KiB
TypeScript
52 lines
1.5 KiB
TypeScript
import { Button } from '@/components/ui/Button'
|
|
import { cn } from '@/lib/cn'
|
|
import type { ChangeSet, UndoOutcome, useUndo } from '@/lib/history'
|
|
|
|
/**
|
|
* Undo, plus what happened. Paired with useUndo so the history list and the
|
|
* agent turn's inline undo (#57) behave identically — including the part that
|
|
* actually matters, which is reporting a partial result honestly rather than as
|
|
* a success or a failure.
|
|
*/
|
|
export function UndoButton({
|
|
changeSet,
|
|
undo,
|
|
className,
|
|
label = 'Undo',
|
|
}: {
|
|
changeSet: ChangeSet
|
|
undo: ReturnType<typeof useUndo>
|
|
className?: string
|
|
label?: string
|
|
}) {
|
|
const outcome = undo.outcomeFor(changeSet.id)
|
|
return (
|
|
<div className={cn('flex flex-col items-end gap-1', className)}>
|
|
<Button
|
|
variant="ghost"
|
|
className="px-2 py-1 text-xs"
|
|
disabled={outcome?.tone === 'pending'}
|
|
onClick={() => undo.undo(changeSet)}
|
|
>
|
|
{outcome?.tone === 'pending' ? 'Undoing…' : label}
|
|
</Button>
|
|
{outcome && outcome.tone !== 'pending' && <OutcomeNote outcome={outcome} />}
|
|
</div>
|
|
)
|
|
}
|
|
|
|
const TONE_CLASS: Record<Exclude<UndoOutcome['tone'], 'pending'>, string> = {
|
|
ok: 'text-muted',
|
|
partial: 'text-amber-700 dark:text-amber-400',
|
|
error: 'text-red-700 dark:text-red-400',
|
|
}
|
|
|
|
function OutcomeNote({ outcome }: { outcome: UndoOutcome }) {
|
|
if (outcome.tone === 'pending') return null
|
|
return (
|
|
<p role="status" className={cn('text-right text-xs', TONE_CLASS[outcome.tone])}>
|
|
{outcome.message}
|
|
</p>
|
|
)
|
|
}
|