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" }); 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", () => { it("shows the ntfy topic field only when ntfy is enabled", () => {
const { rerender } = render( const { rerender } = render(
<NotificationsSection <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> <h4 className="settings-section-heading">{t("settings.notifications.notifications", "Notifications")}</h4>
<div className="notification-provider-card"> <div className="notification-provider-card">
<div className="form-group"> {/*
<label htmlFor="failureNotificationMode">{t("settings.notifications.failureNotificationMode", "Failure notification mode")}</label> FNXC:SettingsLayout 2026-07-11-19:00:
<select id="failureNotificationMode" value={form.failureNotificationMode ?? "sticky-only"} onChange={(e) => { 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.
const value = e.target.value as "sticky-only" | "all" | "terminal-only"; */}
setForm((f) => ({ ...f, failureNotificationMode: value })); <div className="notification-provider-body">
}}> <div className="form-group">
<option value="sticky-only">{t("settings.notifications.stickyFailuresOnlyDefault", "Sticky failures only (default)")}</option> <label htmlFor="failureNotificationMode">{t("settings.notifications.failureNotificationMode", "Failure notification mode")}</label>
<option value="terminal-only">{t("settings.notifications.terminalFailuresOnlySuppressAutoRetried", "Terminal failures only (suppress auto-retried)")}</option> <select id="failureNotificationMode" value={form.failureNotificationMode ?? "sticky-only"} onChange={(e) => {
<option value="all">{t("settings.notifications.allFailuresLegacy", "All failures (legacy)")}</option> const value = e.target.value as "sticky-only" | "all" | "terminal-only";
</select> setForm((f) => ({ ...f, failureNotificationMode: value }));
<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> <option value="sticky-only">{t("settings.notifications.stickyFailuresOnlyDefault", "Sticky failures only (default)")}</option>
<div className="form-group"> <option value="terminal-only">{t("settings.notifications.terminalFailuresOnlySuppressAutoRetried", "Terminal failures only (suppress auto-retried)")}</option>
<label htmlFor="failureNotificationDelayMs">{t("settings.notifications.failureNotificationDelayMs", "Failure notification delay (ms)")}</label> <option value="all">{t("settings.notifications.allFailuresLegacy", "All failures (legacy)")}</option>
<input id="failureNotificationDelayMs" type="number" min={0} step={1000} disabled={(form.failureNotificationMode ?? "sticky-only") === "all"} value={form.failureNotificationDelayMs ?? 30000} onChange={(e) => { </select>
const parsed = Number(e.target.value); <small>{t("settings.notifications.stickyOnlySuppressesRecoveredFailuresTerminalOnlyWaits", "Sticky-only suppresses recovered failures; terminal-only waits for paused/in-review failed tasks; all restores legacy alerts.")}</small>
setForm((f) => ({ </div>
...f, <div className="form-group">
failureNotificationDelayMs: Number.isFinite(parsed) && parsed >= 0 ? parsed : 0, <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);
<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> 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>
</div> </div>