fix(scheduling): harden edit/reschedule after review

Address findings from 3 review agents on the edit/reschedule feature:

- Race (correctness): handleEdit now marks the old message as cancelling
  while its cancel is in flight, so its Edit/Cancel buttons are disabled.
  Previously the old row stayed live during the fire-and-forget cancel, so
  a fast second edit could orphan a still-scheduled event and send twice.

- Durability (correctness): on a failed cancel-old, re-insert the old
  message if auto-prune removed its row while the modal was open, so the
  still-live delayed event stays visible and retriable instead of failing
  silently. Also clear any stale cancel error when starting an edit.

- a11y: per-row Edit/Cancel buttons now carry distinct aria-labels that
  include the message preview and send-time, so screen-reader users can
  tell which of several scheduled messages each button targets.

- UX: modal gains a submitLabel prop; the edit flow shows "Reschedule"
  instead of "Schedule". Modal now focuses the message body on open.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-07-09 22:54:57 -04:00
co-authored by Claude Opus 4.8
parent 5d5ae0ee70
commit cacefb1f30
2 changed files with 51 additions and 11 deletions
@@ -30,6 +30,8 @@ interface ScheduleMessageModalProps {
initialSendAt?: number;
/** Header title; defaults to "Schedule Message". */
title?: string;
/** Primary-button label; defaults to "Schedule" (e.g. "Reschedule" when editing). */
submitLabel?: string;
onScheduled: (delayId: string, sendAt: number, content: IContent) => void;
onClose: () => void;
}
@@ -94,6 +96,7 @@ export function ScheduleMessageModal({
initialBody,
initialSendAt,
title = 'Schedule Message',
submitLabel = 'Schedule',
onScheduled,
onClose,
}: ScheduleMessageModalProps) {
@@ -179,7 +182,7 @@ export function ScheduleMessageModal({
<OverlayCenter>
<FocusTrap
focusTrapOptions={{
initialFocus: false,
initialFocus: '#schedule-message-body',
onDeactivate: onClose,
clickOutsideDeactivates: true,
escapeDeactivates: stopPropagation,
@@ -341,7 +344,7 @@ export function ScheduleMessageModal({
disabled={submitting || !preview}
before={submitting ? <Spinner variant="Primary" size="100" /> : undefined}
>
<Text size="B400">Schedule</Text>
<Text size="B400">{submitLabel}</Text>
</Button>
</Box>
</Dialog>