FN-7828: fix cramped padding on notifications failure-mode settings card

Wraps the failure-notification mode fields in the shared .notification-provider-body wrapper so the card's padding/gutters match sibling provider cards on desktop and mobile.

- Wrap failure-notification mode select and delay input in .notification-provider-body within NotificationsSection.tsx
- Add FNXC:SettingsLayout comment documenting why .notification-provider-body is needed (parent .notification-provider-card has no own padding)
- Add regression test asserting the failure-notification mode field nests inside .notification-provider-body, itself inside .notification-provider-card
- Add changeset (patch) documenting the fix

Files changed:
 .changeset/fuzzy-notification-padding.md           |  7 +++
 .../app/__tests__/settings-sections.test.tsx       | 18 ++++++++
 .../settings/sections/NotificationsSection.tsx     | 50 ++++++++++++----------
 3 files changed, 53 insertions(+), 22 deletions(-)

Fusion-Task-Id: FN-7828

Fusion-Task-Lineage: 734c652d-400e-4cb1-b6a9-3a001f0b10b7

Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
This commit is contained in:
gsxdsm
2026-07-11 18:44:57 -07:00
parent 9d06141808
commit 41998a6c75
3 changed files with 53 additions and 22 deletions

View File

@@ -0,0 +1,7 @@
---
"@runfusion/fusion": patch
---
summary: Fix cramped padding on the Notifications "Failure notification mode" settings card.
category: fix
dev: Wrap the failure-notification card fields in `.notification-provider-body` in NotificationsSection so it matches sibling provider-card padding on desktop and mobile.

View File

@@ -165,6 +165,24 @@ describe("NotificationsSection", () => {
expect(updater(emptyForm)).toMatchObject({ failureNotificationMode: "all" });
});
it("nests the failure-notification mode field inside the padded provider body", () => {
render(
<NotificationsSection
scopeBanner={null}
form={emptyForm}
setForm={vi.fn()}
testNotificationLoading={{}}
testNotificationResult={{}}
onTestProviderNotification={vi.fn()}
/>,
);
const select = screen.getByLabelText("Failure notification mode") as HTMLSelectElement;
const providerBody = select.closest(".notification-provider-body");
expect(providerBody).not.toBeNull();
expect(providerBody?.closest(".notification-provider-card")).not.toBeNull();
});
it("shows the ntfy topic field only when ntfy is enabled", () => {
const { rerender } = render(
<NotificationsSection

View File

@@ -58,28 +58,34 @@ export function NotificationsSection({ scopeBanner, form, setForm, testNotificat
<h4 className="settings-section-heading">{t("settings.notifications.notifications", "Notifications")}</h4>
<div className="notification-provider-card">
<div className="form-group">
<label htmlFor="failureNotificationMode">{t("settings.notifications.failureNotificationMode", "Failure notification mode")}</label>
<select id="failureNotificationMode" value={form.failureNotificationMode ?? "sticky-only"} onChange={(e) => {
const value = e.target.value as "sticky-only" | "all" | "terminal-only";
setForm((f) => ({ ...f, failureNotificationMode: value }));
}}>
<option value="sticky-only">{t("settings.notifications.stickyFailuresOnlyDefault", "Sticky failures only (default)")}</option>
<option value="terminal-only">{t("settings.notifications.terminalFailuresOnlySuppressAutoRetried", "Terminal failures only (suppress auto-retried)")}</option>
<option value="all">{t("settings.notifications.allFailuresLegacy", "All failures (legacy)")}</option>
</select>
<small>{t("settings.notifications.stickyOnlySuppressesRecoveredFailuresTerminalOnlyWaits", "Sticky-only suppresses recovered failures; terminal-only waits for paused/in-review failed tasks; all restores legacy alerts.")}</small>
</div>
<div className="form-group">
<label htmlFor="failureNotificationDelayMs">{t("settings.notifications.failureNotificationDelayMs", "Failure notification delay (ms)")}</label>
<input id="failureNotificationDelayMs" type="number" min={0} step={1000} disabled={(form.failureNotificationMode ?? "sticky-only") === "all"} value={form.failureNotificationDelayMs ?? 30000} onChange={(e) => {
const parsed = Number(e.target.value);
setForm((f) => ({
...f,
failureNotificationDelayMs: Number.isFinite(parsed) && parsed >= 0 ? parsed : 0,
}));
}}/>
<small>{t("settings.notifications.howLongAFailureMustPersistBeforeA", " How long a failure must persist before a push notification is sent. 0 = notify immediately. Default: 30000 (30 seconds). ")}</small>
{/*
FNXC:SettingsLayout 2026-07-11-19:00:
The failure-notification card must reuse `.notification-provider-body` because `.notification-provider-card` has no own padding; this keeps its field gutters aligned with ntfy/webhook provider cards on desktop and mobile.
*/}
<div className="notification-provider-body">
<div className="form-group">
<label htmlFor="failureNotificationMode">{t("settings.notifications.failureNotificationMode", "Failure notification mode")}</label>
<select id="failureNotificationMode" value={form.failureNotificationMode ?? "sticky-only"} onChange={(e) => {
const value = e.target.value as "sticky-only" | "all" | "terminal-only";
setForm((f) => ({ ...f, failureNotificationMode: value }));
}}>
<option value="sticky-only">{t("settings.notifications.stickyFailuresOnlyDefault", "Sticky failures only (default)")}</option>
<option value="terminal-only">{t("settings.notifications.terminalFailuresOnlySuppressAutoRetried", "Terminal failures only (suppress auto-retried)")}</option>
<option value="all">{t("settings.notifications.allFailuresLegacy", "All failures (legacy)")}</option>
</select>
<small>{t("settings.notifications.stickyOnlySuppressesRecoveredFailuresTerminalOnlyWaits", "Sticky-only suppresses recovered failures; terminal-only waits for paused/in-review failed tasks; all restores legacy alerts.")}</small>
</div>
<div className="form-group">
<label htmlFor="failureNotificationDelayMs">{t("settings.notifications.failureNotificationDelayMs", "Failure notification delay (ms)")}</label>
<input id="failureNotificationDelayMs" type="number" min={0} step={1000} disabled={(form.failureNotificationMode ?? "sticky-only") === "all"} value={form.failureNotificationDelayMs ?? 30000} onChange={(e) => {
const parsed = Number(e.target.value);
setForm((f) => ({
...f,
failureNotificationDelayMs: Number.isFinite(parsed) && parsed >= 0 ? parsed : 0,
}));
}}/>
<small>{t("settings.notifications.howLongAFailureMustPersistBeforeA", " How long a failure must persist before a push notification is sent. 0 = notify immediately. Default: 30000 (30 seconds). ")}</small>
</div>
</div>
</div>