Context
Issue #21 listed "alert threshold" among the page detail controls. It came out of that issue's scope because there is nothing to build it against: the control implies per-page alert settings, and the data model has only half of one.
What exists today:
accounts.alert_threshold — per account, on the wire in AccountResponse, and the only threshold detection reads.
pages.alerts_enabled — per page, added by migration 008, selected in postgres-page-repository.ts, and set to false by the unsubscribe token flow. It is not in PageModel, not in @tabstop/contract, and not settable through the API.
PATCH /api/pages/:id accepts monitoringEnabled and nothing else.
So a page can be silenced by clicking unsubscribe in an email, and then never un-silenced from the product. That is the actual bug hiding behind the missing control.
Scope
In
- Expose
alertsEnabled on the page wire types and let PATCH /api/pages/:id set it.
- A per-page alerts toggle on page detail, beside pause and remove, so a page silenced by an unsubscribe link can be turned back on.
- Say what the toggle means: pausing stops the audits, silencing stops the emails. Per
DECISIONS.md these are deliberately different actions and the UI should not blur them.
Out
- A per-page threshold override. There is no column for it, and it is a larger question than a control: whether the threshold belongs to the account, the page, or both, and what happens to detection when they disagree. Worth its own issue if the demand appears.
- Any change to detection, to the dispatcher, or to the unsubscribe token.
Implementation sketch
Server, mirroring how monitoringEnabled already flows:
PageModel gains alertsEnabled; the mapper already selects the column.
PageView in contract/src/pages.ts gains alertsEnabled: boolean.
updatePageSchema gains an optional strict boolean — optional, so an existing client sending only monitoringEnabled keeps working, and strict for the reason already recorded there: z.coerce.boolean() maps "false" to true.
Web:
- The toggle sits in the page controls and reuses the row-scoped optimistic pattern from
useSetPageMonitoring, including the rollback that restores only the affected page.
The two states are independent, and all four combinations are reachable: monitored and alerting, monitored and silent, paused and alerting (which sends nothing, because nothing is audited), paused and silent. The copy has to make the first two distinguishable at a glance, since that is the pair a reader will actually be choosing between.
Acceptance criteria
Split out of #21.
Context
Issue #21 listed "alert threshold" among the page detail controls. It came out of that issue's scope because there is nothing to build it against: the control implies per-page alert settings, and the data model has only half of one.
What exists today:
accounts.alert_threshold— per account, on the wire inAccountResponse, and the only threshold detection reads.pages.alerts_enabled— per page, added by migration 008, selected inpostgres-page-repository.ts, and set to false by the unsubscribe token flow. It is not inPageModel, not in@tabstop/contract, and not settable through the API.PATCH /api/pages/:idacceptsmonitoringEnabledand nothing else.So a page can be silenced by clicking unsubscribe in an email, and then never un-silenced from the product. That is the actual bug hiding behind the missing control.
Scope
In
alertsEnabledon the page wire types and letPATCH /api/pages/:idset it.DECISIONS.mdthese are deliberately different actions and the UI should not blur them.Out
Implementation sketch
Server, mirroring how
monitoringEnabledalready flows:PageModelgainsalertsEnabled; the mapper already selects the column.PageViewincontract/src/pages.tsgainsalertsEnabled: boolean.updatePageSchemagains an optional strict boolean — optional, so an existing client sending onlymonitoringEnabledkeeps working, and strict for the reason already recorded there:z.coerce.boolean()maps"false"to true.Web:
useSetPageMonitoring, including the rollback that restores only the affected page.The two states are independent, and all four combinations are reachable: monitored and alerting, monitored and silent, paused and alerting (which sends nothing, because nothing is audited), paused and silent. The copy has to make the first two distinguishable at a glance, since that is the pair a reader will actually be choosing between.
Acceptance criteria
alertsEnabledis on the page wire types and settable throughPATCH /api/pages/:id.monitoringEnabledstill works.Split out of #21.