Files
fusion/packages/dashboard/app/components/settings/sections/NotificationsSection.search.ts
gsxdsm 6ae9299576 FN-8953: defer terminal wedge alerts during recovery
Hold terminal wedge alerts until their recovery window has elapsed.

- Persist and settle pending wedge notifications across restarts.
- Clear pending alerts on task progress and reconcile expired holds during self-healing.
- Expose the settle window in notification settings with coverage for store and notification flows.

Files changed:
 .changeset/fn-8953-wedge-settle-window.md          |   7 +
 AGENTS.md                                          |   1 +
 docs/architecture.md                               |   3 +-
 docs/settings-reference.md                         |   1 +
 .../core/src/__tests__/store-wedge-pending.test.ts |  56 +++++
 packages/core/src/config/settings-schema.ts        |   1 +
 packages/core/src/store.ts                         |  46 ++++
 packages/core/src/types/settings/settings-scope.ts |   2 +
 packages/core/src/types/task/task-core.ts          |  15 ++
 .../app/components/settings/save-split.ts          |   1 +
 .../sections/NotificationsSection.search.ts        |   9 +
 .../settings/sections/NotificationsSection.tsx     |  15 ++
 .../settings-default-descriptions.test.tsx         |   1 +
 ...self-healing-pending-wedge-notification.test.ts | 148 ++++++++++++
 .../__tests__/notification-service.test.ts         |   7 +-
 .../__tests__/task-wedge-notification.test.ts      | 258 ++++++++++++++++++++-
 .../src/notification/notification-service.ts       | 260 +++++++++++++++++++++
 packages/engine/src/self-healing.ts                |  38 +++
 packages/i18n/locales/en/app.json                  |   2 +
 19 files changed, 850 insertions(+), 21 deletions(-)

Fusion-Task-Id: FN-8953

Fusion-Task-Lineage: fd5b5827-c69d-409f-86d5-01ff23405ee3

Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
2026-08-11 12:34:47 -07:00

105 lines
5.4 KiB
TypeScript

/**
* Search entries for the Notifications section.
*
* FNXC:SettingsSearch 2026-07-15-17:35:
* One entry per descriptor row the section renders, co-located so a setting and its index entry change in the same edit. Labels and help mirror the section's `t()` calls verbatim: the index matches on the copy operators actually read, so a paraphrase here would make search miss the words on screen.
* The section's still-bespoke controls are deliberately absent — they are not descriptor rows, so indexing them would point search at an anchor that does not exist: the ntfy/webhook Enable switches (card headers), the ntfy topic + Advanced disclosure (server URL, access token), the per-event checkbox grids, and the Test notification buttons.
*/
import type { SettingsSearchEntry } from "../search/types";
export const notificationsSearchEntries: SettingsSearchEntry[] = [
{
sectionId: "notifications",
key: "failureNotificationMode",
labelKey: "settings.notifications.failureNotificationMode",
labelFallback: "Failure notification mode",
helpKey: "settings.notifications.stickyOnlySuppressesRecoveredFailuresTerminalOnlyWaits",
helpFallback:
"Sticky-only suppresses recovered failures; terminal-only waits for paused/in-review failed tasks; all restores legacy alerts.",
/*
FNXC:SettingsSearch 2026-07-15-17:35:
"retry" and "noise" are indexed because this control is what an operator reaches for when auto-retried failures are spamming them — vocabulary the copy never uses, since it describes the modes rather than the problem they solve.
*/
keywords: ["retry", "noise", "alerts"],
},
{
sectionId: "notifications",
key: "failureNotificationDelayMs",
labelKey: "settings.notifications.failureNotificationDelayMs",
labelFallback: "Failure notification delay (ms)",
helpKey: "settings.notifications.howLongAFailureMustPersistBeforeA",
helpFallback:
" How long a failure must persist before a push notification is sent. 0 = notify immediately. Default: 30000 (30 seconds). ",
keywords: ["debounce", "wait", "throttle"],
},
{
sectionId: "notifications",
key: "wedgeNotificationSettleMs",
labelKey: "settings.notifications.wedgeNotificationSettleMs",
labelFallback: "Terminal-wedge settle window (ms)",
helpKey: "settings.notifications.wedgeNotificationSettleMsHelp",
helpFallback: "How long a terminal failure must persist before an operator alert. 0 = notify immediately. Default: 300000 (5 minutes).",
keywords: ["noise", "debounce", "wedge", "recovered"],
},
{
sectionId: "notifications",
key: "ntfyDashboardHost",
labelKey: "settings.notifications.dashboardHostname",
labelFallback: "Dashboard Hostname",
helpKey: "settings.notifications.baseURLForDeepLinksInNotificationsWhen",
helpFallback:
" Base URL for deep links in notifications. When set, clicking a notification opens the dashboard directly to the task. No default — unset. ",
/*
FNXC:SettingsSearch 2026-07-15-17:35:
"ntfy" is a keyword rather than left to the copy: the stored key is `ntfyDashboardHost` and the row renders inside the ntfy provider card, but its label and help never say "ntfy", so an operator scanning for their ntfy configuration would otherwise miss it.
*/
keywords: ["ntfy", "deep link", "base url"],
},
{
sectionId: "notifications",
key: "webhookUrl",
labelKey: "settings.notifications.webhookURL",
labelFallback: "Webhook URL",
helpKey: "settings.notifications.webhookUrlHint",
helpFallback: "No default — unset.",
keywords: ["endpoint", "hook", "callback"],
},
{
sectionId: "notifications",
key: "webhookFormat",
labelKey: "settings.notifications.format",
labelFallback: "Format",
helpKey: "settings.notifications.webhookFormatHint",
helpFallback: "Default: generic.",
/*
FNXC:SettingsSearch 2026-07-15-17:35:
The destinations this setting selects between live in the option labels, which the index does not read — only the row's label and help. "Slack"/"Discord" are the words an operator actually searches, and the label is the single word "Format", so without these keywords the row is effectively unfindable.
*/
keywords: ["slack", "discord", "payload", "webhook"],
},
{
sectionId: "notifications",
key: "ntfyBaseUrl",
labelKey: "settings.notifications.customNtfyServerURLOptional",
labelFallback: "Custom ntfy server URL (optional)",
helpKey: "settings.notifications.leaveBlankToKeepTheDefaultServerHttps",
helpFallback:
" Leave blank to keep the default server: https://ntfy.sh. Custom servers must use http:// or https://. No default — unset. ",
keywords: ["self-hosted", "custom server"],
},
{
sectionId: "notifications",
key: "ntfyAccessToken",
labelKey: "settings.notifications.accessTokenOptional",
labelFallback: "Access token (optional)",
helpKey: "settings.notifications.leaveBlankToPublishWithoutAuthenticationWhenSet",
helpFallback:
" Leave blank to publish without authentication. When set, Fusion sends an Authorization Bearer header with ntfy requests. No default — unset. ",
/*
FNXC:SettingsSearch 2026-07-15-18:52:
Only the label and help are indexed — never the stored value. The index is a module-scope constant of static copy, so a secret cannot reach it.
*/
keywords: ["auth", "bearer", "credential", "secret"],
},
];