Repository navigation
Notifications: Dismiss for questions and approvals - #667
Conversation
The Notifications page showed Dismiss for `notify` rows only. The dismiss endpoint accepted a pending question, but it only changed the status: other tabs, the Telegram and Slack cards, "Needs you" and the asking agent kept the question open. A dismissed question or approval now ends without an answer, as at expiry: - every single-row dismissal sends a `notification_dismissed` broadcast; - the Telegram and Slack cards get "Dismissed without an answer" and lose their buttons; - the session that asked a question gets the internal turn "[Question dismissed without an answer: <title>]", written by the person who dismissed it (no turn for external, archived or missing sessions; refused with 503 during a drain, so the question stays pending); - an approval gets an `approval-dismissed` audit record. An approval that a press claimed is not dismissed, because its dispatcher runs the decision. The web UI shows Dismiss on every pending row of the Notifications page and under the options of the `ask_user` card in the chat. The event folds every copy of the row, lowers the pending counts, reloads the session list and closes the system alert. Dismiss All still takes `notify` rows only. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Nerve PR review · ✅ Ready to merge This closes a real gap.
Completeness: backend tests cover each guard (claimed, snoozed, drain, external, archived) and add a route test through the real engine; the frontend has specs; Checked: 11 notification, drain, review-loop and Telegram test files in an offline sandbox at |
What
A pending question or approval gets a Dismiss button:
notifyrows already had it);ask_usercard in the chat.A dismissed question or approval ends without an answer, the same way as at expiry.
Why
The Notifications page showed Dismiss for
notifyrows only.POST /api/notifications/{id}/dismissalready accepted a pending question, buthandle_dismissonly changed the status. Other tabs and devices, the Telegram and Slack cards (their buttons stayed live), the "Needs you" group and the agent that asked all kept the question open.How
Backend (
NotificationService.handle_dismiss):notification_dismissedbroadcast on__global__(notification_id,session_id,notification_type,title,actor_id)._edit_telegram_closed,_edit_slack_closed).[Question dismissed without an answer: <title>], written by the person who dismissed it. The checks for external, archived and missing sessions move to_takes_note, which expiry also uses. During a drain, such a dismissal is refused (503) and the question stays pending, ashandle_answerdoes.approval-dismissedrecord in the mechanical-actions audit log.dismiss_notificationnow also requiresanswered_at IS NULL, so an approval that a press claimed (its dispatcher is running) is not dismissed.notifyrows only, no event.Frontend:
DismissButton(shared by the page and the chat card). On a refusal, the store reads the row again: an answer from a different place replaces the button; otherwise "Could not dismiss. Try again." shows.notification_dismissedhandler: folds every copy of the row (list, toast, chat) to "Dismissed without an answer" withdismissed_by_actor_id, lowers the pending counts when the row was pending, reloads the session list ("Needs you") and closes the system alert. A toast for the row folds and closes after 5 s, as for an answer from a different place.Docs:
api.md(both dismiss endpoints, the new event),web-ui.md,architecture.md.Screenshots
Synthetic data from a throwaway instance.
After Dismiss, the Notifs badge goes from 3 to 2 and the session leaves "Needs you". Phone, light theme:
Tests
TestDismissalintests/test_notification_lifecycle.py(broadcast, note, Telegram and Slack edits, approval audit, claimed and snoozed approvals, rows that are not pending, external and archived sessions, drain) and one route test through the real engine intests/test_notification_plan_attribution.py(the note's actor, the session's cause, the 409 on a second dismissal). Full suite: 8823 passed, 39 skipped, equal to the 8862 collected.npm run buildpasses;npm test: 141 files, 1908 tests pass.🤖 Generated with Claude Code