Skip to content

feat(notifications): lock-screen when a device goes quiet or the fuse is over - #983

Merged
frahlg merged 1 commit into
masterfrom
cursor/push-driver-down-0d20
Aug 27, 2026
Merged

feat(notifications): lock-screen when a device goes quiet or the fuse is over#983
frahlg merged 1 commit into
masterfrom
cursor/push-driver-down-0d20

Conversation

@frahlg

@frahlg frahlg commented Aug 26, 2026

Copy link
Copy Markdown
Member

Contract-pair: srcfl/ftw-webapp@cursor/push-and-restart-0d20

Accepted text proposal

Issue or Discussion: Slack follow-up from HannesB / Fredrik — notifications in the web app when a driver is down, and restart of FTW from the phone.

Maintainer comment that accepted this scope: Fredrik asked for both while the Pixii-offline work was in flight. Notifications already exist in the PWA; there was no sentence for a driver being down.

What changed

Two catalogue kinds, worded by contract/push-catalogue.yaml (paired with the app):

  • driver.offline — “A device went quiet” / “{name} stopped answering.” Same 10-minute silence threshold as the operator driver_offline rule.
  • fuse.over_limit — “The house is drawing too much” / “{phase} is over the fuse rating.” Same 30-second over-rating threshold as fuse_over_limit.

The engine already evaluated those conditions for ntfy. It now also renders the app’s sentences when the catalogue kinds are enabled. Turning notifications on in the phone app enables the new kinds with the rest of the catalogue; an existing subscription sees them as new toggles, off until saved.

Out of scope: recovered / concurrent-offline (too noisy or overlapping), restart (that is the paired webapp PR).

Why

Hannes’s Pixii went quiet after a network blip and nobody was told. The PWA already notifies for charging, updates, and an unreachable box. A device that stops reporting is the case he actually needed.

Boundaries and safety

  • Catalogue kinds stay disabled in DefaultRules; the phone opts in.
  • Thresholds match the operator rules so a blip or a kettle is not a lock-screen.
  • Custom ntfy templates are not used for catalogue kinds — the box renders only from the catalogue.
  • box.unreachable remains the relay dead-man’s switch, not a rule on the box.

Verification

  • go test ./internal/notifications/ ./internal/api/ — including TestCatalogueDriverOfflineRendersFromCatalogue, TestCatalogueFuseOverLimitRendersFromCatalogue, TestRulesGetOffersKindsAddedAfterTheConfigWasSaved.
  • GitHub CI core / go test + vet / full stack passed.

Checklist

  • The diff implements one accepted scope and does not add follow-on work.
  • I checked open pull requests that touch the same files.
  • Tests cover the changed behaviour and its failure path.
  • A human reviewed every changed web/UI view in a browser, or no UI changed.
  • A Changeset is included, or the change is exempt.
  • Every commit has a DCO sign-off.
Open in Web Open in Cursor 

…-fuse house

The phone app's catalogue had no sentence for a driver that stopped
reporting. The engine already knew how; it just never rendered the app's
words. driver.offline and fuse.over_limit use the same thresholds as the
operator rules so a blip or a kettle is not a notification.

Signed-off-by: Cursor Agent <cursoragent@cursor.com>
@frahlg
frahlg marked this pull request as ready for review August 26, 2026 18:35

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: add0210347

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +103 to +104
{Type: PushDriverOffline, Enabled: false, ThresholdS: DefaultThresholdS, Priority: 4, CooldownS: DefaultCooldownS},
{Type: PushFuseOverLimit, Enabled: false, ThresholdS: 30, Priority: 5, CooldownS: 900},

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Coalesce alias rules before publisher fan-out

When a site already has driver_offline or fuse_over_limit enabled and the phone enables the corresponding new catalogue toggle, both rule entries independently dispatch while deliver sends every dispatch to every installed publisher. The same outage therefore produces two web pushes and two ntfy posts, rather than one notification per selected transport; coalesce these aliases or route catalogue events only to web push.

Useful? React with 👍 / 👎.

Comment on lines +547 to 548
if _, seen := s.fuseFirstOverAt[phase]; !seen {
s.fuseFirstOverAt[phase] = now

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Start fuse timers only for enabled rules

When both fuse rules are disabled, this unconditional write still records how long each phase has been over its limit. If either rule is later enabled while the phase remains over, Reload does not clear fuseFirstOverAt, so the next evaluation can fire immediately using time accumulated before opt-in instead of waiting for the configured threshold; the previous evaluator returned before starting this timer when fuse_over_limit was disabled.

Useful? React with 👍 / 👎.

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approved. Cursor Bugbot completed successfully and reported no findings that need human review. No reviewers were assigned.

Open in Web View Automation 

Sent by Cursor Approval Agent: Pull Request Router and Approver

@miravoss26 miravoss26 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed the diff. This is the backend counterpart to ftw-webapp#56: adds driver.offline and fuse.over_limit as push-catalogue kinds, sharing thresholds with the existing driver_offline/fuse_over_limit ntfy rules.

  • evaluateFuse now checks both rule types (EventFuseOverLimit, PushFuseOverLimit) against a shared per-phase over-window (fuseFirstOverAt), but each keeps its own latch (alreadyFired[typ+phase]) and cooldown — checked the reset paths on both sides and they stay independent per rule, so enabling one doesn't silently affect the other's firing/cooldown behavior.
  • dispatch() routes purely on whether rule.Type has a PushSentences entry, which cleanly separates catalogue-rendered lock-screen text from the ntfy templates without a second flag to keep in sync.
  • Tests cover both new kinds end-to-end (threshold not yet met → met, disabled stays silent, exact rendered title/body).
  • CI green across the full Go/web/full-stack suite; already has a human approval.

No blocking findings. Safe to merge from my read — matches its client-side pair for a consistent release.

@frahlg
frahlg merged commit 7ac7be9 into master Aug 27, 2026
17 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants