fix(ui): add space above failed attempt notices (#18636)

Add token-based top padding outside failed-attempt notices and assert the full 16px gap in the browser check.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
DottaandPaperclip authored and GitHub committed 2026-11-02 12:10:43 -06:00
1 parent 5f0bb518b0
commit 6aabcff7d1
3 files changed
+15 -12

No files matched your search

+2 -2
View File
@@ -117,8 +117,8 @@ completes without a provider reply. Preserve the reply through persistence.
Task chat shows execution errors and waits only while they remain relevant.
Exhausted attempts use a padded, rounded notice with a destructive-tinted background,
an error icon, and foreground text. The surrounding layout supplies separation from
the conversation. Automatic retry countdowns keep their quieter inline styling.
an error icon, and foreground text. A wrapper adds top padding above the tinted
surface. Automatic retry countdowns keep their quieter inline styling.
Completing or cancelling a task hides its old execution notices. A newer attempt by
the same agent or an explicit successor supersedes earlier run notices; an
unresolved execution hold remains visible. Historical turns keep their responses,
@@ -24,10 +24,12 @@ test.describe("failed-attempt notice", () => {
return {
background: style.backgroundColor,
padding: [style.paddingTop, style.paddingRight, style.paddingBottom, style.paddingLeft].map(parseFloat),
topPadding: parseFloat(getComputedStyle(element.parentElement!).paddingTop),
};
});
expect(surface.background).not.toBe("rgba(0, 0, 0, 0)");
expect(surface.padding.every(value => value > 0)).toBe(true);
expect(surface.topPadding).toBeCloseTo(16);
const iconBounds = (await icon.boundingBox())!;
const textBounds = (await message.boundingBox())!;
+11 -10
View File
@@ -28,16 +28,17 @@ export function ExecutionBlockerNotice({ presentation, canRetry, onRetry, optimi
!["failed", "retry_scheduled"].includes(presentation.state)) return null;
const seconds = Math.max(0, Math.ceil((Date.parse(presentation.nextRetryAt ?? "") - Date.now()) / 1000));
return (
<div role="status" aria-label="Task status" className={cn(
"flex items-center justify-between gap-3 text-sm",
presentation.state === "failed" ? "rounded-lg bg-destructive/10 p-4 text-foreground" : "text-muted-foreground",
className,
)}>
{presentation.state === "failed" && <AlertCircle className="size-5 shrink-0 text-destructive" aria-hidden="true" />}
<span className="min-w-0 flex-1">{presentation.state === "retry_scheduled"
? `Trying again in ${seconds > 60 ? `${Math.ceil(seconds / 60)}m` : `${seconds}s`}…`
: "Couldn’t finish this attempt. Your message is saved."}</span>
{canRetry && presentation.canRetry && <Button variant="outline" size="sm" className="shrink-0" onClick={onRetry}>Retry</Button>}
<div className={cn(presentation.state === "failed" && "pt-4", className)}>
<div role="status" aria-label="Task status" className={cn(
"flex items-center justify-between gap-3 text-sm",
presentation.state === "failed" ? "rounded-lg bg-destructive/10 p-4 text-foreground" : "text-muted-foreground",
)}>
{presentation.state === "failed" && <AlertCircle className="size-5 shrink-0 text-destructive" aria-hidden="true" />}
<span className="min-w-0 flex-1">{presentation.state === "retry_scheduled"
? `Trying again in ${seconds > 60 ? `${Math.ceil(seconds / 60)}m` : `${seconds}s`}…`
: "Couldn’t finish this attempt. Your message is saved."}</span>
{canRetry && presentation.canRetry && <Button variant="outline" size="sm" className="shrink-0" onClick={onRetry}>Retry</Button>}
</div>
</div>
);
}