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>
105 lines
5.4 KiB
TypeScript
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"],
|
|
},
|
|
];
|