The notification now owns its payload via #payload method in Pushable,
allowing direct access like notification.payload.title. Push classes
simply use the notification's payload rather than building it themselves.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Separates notification payload construction from push delivery by
introducing DefaultPayload, EventPayload, and MentionPayload classes
that encapsulate the title, body, and URL generation for each
notification type.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Replace NotificationPusher with a cleaner architecture:
- Add Notification::Pushable concern with push target registry
- Add Notification::Push base class with template methods
- Add Notification::Push::Web for web push (OSS)
- Add Notification::Push::Native for native push (SaaS)
- Add Notification::WebPushJob and Notification::NativePushJob
Key design:
- Registry pattern: Notification.register_push_target(:web)
- Template method: push calls should_push? then perform_push
- Subclasses override should_push? (with super) and perform_push
- Each target handles its own job enqueueing
Also:
- Add Notification#pushable? for checking push eligibility
- Add Notification#identity delegation to user
- Reorganize tests to match new class structure
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Tidy up saas engine a bit more
Include identity.id in the my/identity.json response and return an
empty body from the CREATE signup/completions.json endpoint.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Event notifiers used the `mentionees` DB association to exclude mentioned
users from comment/card notifications. Since mentions are created async
via Mention::CreateJob, a race condition meant the mentionee list could
be empty when the event notification job ran first, causing the user to
receive both a comment and a mention push notification.
Use `scan_mentionees` instead, which scans the rich text body directly
for mentioned users without depending on Mention records existing yet.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Rails only applies the last callback when `after_create_commit` and
`after_update_commit` reference the same method name [1]:
> However, if you use the `after_create_commit` and the
`after_update_commit` callback with the same method name, it will only
allow the last callback defined to take effect, as they both internally
alias to `after_commit` which overrides previously defined callbacks
with the same method name.
- Push notifications were never sent when a notification was first
created — only when the source was updated
- Replaced the two callbacks with a single `after_save_commit`, which
fires on both create and update, with the
`source_id_previously_changed?` guard (true for both new records and
source changes)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
[1] https://guides.rubyonrails.org/active_record_callbacks.html#aliases-for-after-commit
Revert "Add bridged share button to board and card permas"
This reverts commit 069e165b43.
Revert "Move helper to bridge helper file"
This reverts commit 1ee37f046c.
The stemmer was concatenating tokens across hyphens (e.g. "BC3-IOS-1D8B"
→ "bc3ios1d8b") while the query sanitizer split on them, causing MySQL
MATCH AGAINST to never find the indexed token. Replace non-word chars
with spaces instead of stripping them so indexing and querying tokenize
consistently.
Requires search:reindex after deploy to rebuild existing search records.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>