Each row on a member’s Unread and Read notification screens has Read (or Unread) and Delete buttons. Each button is its own POST form to the screen’s canonical URL, with the action, the notification ID and BuddyPress’s own nonce for that action in the body:
<form method="post" action="/members/{member}/notifications/">
<input type="hidden" name="bp_localhero_notification_action" value="delete" />
<input type="hidden" name="notification_id" value="123" />
<input type="hidden" name="_wpnonce" value="…" />
<button type="submit" class="wp-element-button delete confirm bp-tooltip">Delete</button>
</form>
mark_read, mark_unread and delete, checked against core’s nonces bp_notification_mark_read_{id}, bp_notification_mark_unread_{id} and bp_notification_delete_{id}.bp_localhero_theme_action_notification() handles the post on bp_actions at priority 9, before core’s handlers, using core’s functions and access checks (bp_notifications_delete_notification(), bp_notifications_mark_notification()). It adds core’s success or error notice and redirects back to the screen with 303 See Other.unread slug; the Read screen’s is …/notifications/read/.bp_localhero_theme_notification_action_form( $action, $notification_id, $label, $class ) to output a button. The bulk actions form after the table is unchanged.Core’s single-notification handlers read the action, ID and nonce from $_GET, and their URLs include the unread slug, which is not canonical. Up to 0.1.41 the pack posted to those URLs as they were. That only worked through BuddyPress’s canonical redirect, which bp_redirect_canonical() skips whenever $_POST is not empty, so as soon as any script added a field to the form (LH Localforage adds tracking data to every POST form) nothing was deleted or marked. Posting to the canonical URL with everything in the body keeps state changes out of URLs and does not depend on that redirect.