Commit Graph

9498 Commits

Author SHA1 Message Date
Rosa Gutierrez dbfb141b6f Restore PushNotificationJob as shim for in-flight jobs during deploy
In-flight jobs queued before deploy would fail if the old class is
missing. This shim delegates to the new notification.push method.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-25 19:31:13 +01:00
Rosa Gutierrez 126ccb5e3a Add integration test for notification delivery and simplify test helpers
- Add comprehensive integration test covering card assignment, comments,
  mentions, email bundling, and edge cases (system user, inactive user)
- Inline push notification test helpers into the only test that uses them
- Remove separate PushNotificationTestHelper module

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-25 19:31:13 +01:00
Rosa Gutierrez 1a0d8e2501 Fix notification click URL by using correct data property
The web push payload sends the URL in data.url but the service
worker was looking for data.path, resulting in undefined URLs.

Also fix WebPush::Notification test to use url instead of path

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-25 19:31:13 +01:00
Rosa Gutierrez 155fa2da97 Go back to RubyGems version of action_push_native
After releasing the new version: https://github.com/rails/action_push_native/releases/tag/v0.3.1
2026-02-25 19:31:13 +01:00
Rosa Gutierrez 6b1598eebb Configure APNS and FCM encryption keys in Kamal secrets
Move encryption keys to base64 password fields for easier Kamal secret
management, and organize them at the root level.
2026-02-25 19:31:13 +01:00
Rosa Gutierrez 7864748be9 Simplify PushTarget by removing template method pattern
Each target now implements process directly with its own logic,
rather than using processable?/perform_push hooks. The pushable?
check is done once in Notification#push before iterating targets.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-25 19:31:13 +01:00
Rosa Gutierrez 0948533da7 Rename push to process on PushTarget for clearer semantics
"Push to target" reads naturally - we push the notification to the
target. "Target processes" also makes sense - the target receives
and handles the notification in its own way.

- Add class method PushTarget.process(notification) that instantiates
  and calls the instance method
- Rename instance method from push to process
- Add private push_to helper in Pushable for readable iteration

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-25 19:31:13 +01:00
Rosa Gutierrez 3621df8f0f Consolidate push jobs into single Notification::PushJob
Replace separate WebPushJob and NativePushJob with a single PushJob
that calls notification.push, which iterates over registered targets.
Each target handles its own delivery - Web pushes synchronously via
the pool, Native enqueues device-level jobs via deliver_later_to.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-25 19:31:13 +01:00
Rosa Gutierrez 4f19c42958 Move category and high_priority to payload classes
Use polymorphism instead of case statements in Native push target:
- DefaultPayload#category returns "default", #high_priority? returns false
- EventPayload#category returns "assignment"/"comment"/"card" based on action
- MentionPayload#category returns "mention", #high_priority? returns true

This simplifies the Native push target by delegating source-specific
logic to the appropriate payload classes.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-25 19:31:13 +01:00
Rosa Gutierrez 9299300dbf Move push priority concerns from Event and Mention into Native push target 2026-02-25 19:31:13 +01:00
Rosa Gutierrez 34fd62faf1 Move devices table to saas database
Use ActionPushNative's new on_load hook to configure the database connection,
following the same pattern as Active Storage and Action Text:

  ActiveSupport.on_load(:action_push_native_record) do
    connects_to database: { writing: :saas, reading: :saas }
  end

This allows ApplicationPushDevice to inherit directly from ActionPushNative::Device
without needing an intermediate abstract class.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-25 19:31:13 +01:00
Rosa Gutierrez 1b53396050 Make devices controller untenanted with engine routes
- Add disallow_account_scope to skip tenant requirement
- Move routes to saas/config/routes.rb (engine routes)
- Use saas.devices_path/saas.device_path for engine route helpers
- Update tests to work without tenant context

Devices belong to Identity (global), not Account, so they don't
need tenant context in the URL.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-25 19:31:13 +01:00
Fernando Olivares c7d0559191 Change priority notification level for mentions and assignments 2026-02-25 19:31:13 +01:00
Rosa Gutierrez 2f9e41d356 Remove foreign key constraint from devices to sessions
Devices should persist independently of sessions - when a session is
deleted, the device registration should remain valid.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-25 19:31:13 +01:00
Rosa Gutierrez 8a77fd7966 Squash device migrations into single table creation
Consolidates the session reference and index cleanup into the original
CreateActionPushNativeDevices migration for a cleaner migration history.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-25 19:31:13 +01:00
Rosa Gutierrez 92f283c4d9 Remove redundant owner index from devices table
The unique (owner_type, owner_id, token) index already serves queries
filtering by (owner_type, owner_id) via its leftmost prefix.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-25 19:31:13 +01:00
Rosa Gutierrez ab6bc256eb Link devices to sessions for automatic cleanup on logout
When a session is destroyed (user logs out), all devices registered
to that session are automatically destroyed, preventing push
notifications from being sent to logged-out devices.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-25 19:31:13 +01:00
Rosa Gutierrez b5205ce6b2 Rename Push to PushTarget for better readability
PushTarget#push reads more naturally than Push#push. The push target
is the thing that pushes notifications to a specific destination.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-25 19:31:13 +01:00
Rosa Gutierrez 06c9ece058 Move payload method to Notification and make accessors public
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>
2026-02-25 19:31:13 +01:00
Rosa Gutierrez 1d2ed91e89 Extract payload building into dedicated classes
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>
2026-02-25 19:31:13 +01:00
Rosa Gutierrez 05819f84a2 Refactor notification push system with registry pattern
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
2026-02-25 19:31:13 +01:00
Rosa Gutierrez 555132bbef Change device ownership from User to Identity
Devices now belong to Identity instead of User, allowing a single
device registration to work across all accounts an identity has
access to.

- Move User::Devices to Identity::Devices
- Update DevicesController to use Current.identity
- Update NotificationPusher::Native to use user.identity.devices
- Clean up tests to use @identity directly

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

Fix reference to `user.devices`, left-over from the identity switch
2026-02-25 19:31:13 +01:00
Rosa Gutierrez d3cc79a5bc Simplify device routes and use ActiveRecord validations
- Use RESTful DELETE /devices/:id where :id can be token or database ID
- Remove redundant unregister collection route
- Remove old Users::DevicesController
- Return 404 when device not found instead of silently succeeding
- Return 422 for invalid platform via ActiveRecord validation
- Update action_push_native to main branch (includes validate: true on enum)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-25 19:31:13 +01:00
Rosa Gutierrez 9d32f3e845 Refactor devices controller and extract registration to model
- Remove Users namespace from DevicesController (now just DevicesController)
- Create ApplicationPushDevice model extending ActionPushNative::Device
- Move device registration logic (find_or_initialize + update) to model
- Update User::Devices concern to use ApplicationPushDevice
- Fix push notification tests (endpoint validation, job count expectations)
- Update push_config_test to use ActionPushNative.config

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-25 19:31:13 +01:00
Fernando Olivares 7e0470a692 Send the URL instead of path in notifications 2026-02-25 19:31:13 +01:00
Fernando Olivares ade39b15ab Add title/body to android notifications too 2026-02-25 19:31:13 +01:00
Fernando Olivares 29d3960a3c Remove UUID requirement from push notification device registration
- Remove UUID column from devices table entirely
- Update unique index from (owner, uuid) to (owner, token)
- Simplify create action to just create device records
- Add token-based unregister route for API clients
- Consolidate error handling with rescue_from
- Update fixtures to remove uuid references

Devices are now identified by (owner, token) instead of UUID.
This simplifies the client-side registration flow.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-25 19:31:13 +01:00
Rosa Gutierrez 2121b6dc3d Use version of action_push_native with proper config paths support
See https://github.com/rails/action_push_native/pull/89

Now we can delete the OSS version of `config/push.yml`, no longer needed.
2026-02-25 19:31:13 +01:00
Fernando Olivares 7d04bb514f Add Firebase configuration
- Update local script to load Firebase key
- Configure Firebase projectId in push.yml
2026-02-25 19:31:13 +01:00
Fernando Olivares 55336873b2 Fix database issues and add APNS configuration
- Fix owner_id type to UUID in devices migration
- Fix NOT NULL crash in device registration
- Add APNS config and 1Password integration
- Add --apns flag to bin/dev for local development
- Clean up devices controller
2026-02-25 19:31:13 +01:00
Fernando Olivares 3c54cd84fc Add native push notification infrastructure
- Add action_push_native gem and SaaS configuration
- Add device registration API and UI
- Add User::Devices concern
- Add NotificationPusher::Native for push delivery
- Add tests and fixtures for native push
2026-02-25 19:31:13 +01:00
Rosa Gutierrez 628a43ef61 Small native API changes: include identity ID, return empty signup completion body
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>
2026-02-25 14:34:09 +01:00
Samuel Péchèr 988dc637da Merge pull request #2606 from basecamp/lexxy-0.7.6.beta
Update Lexxy to 0.7.6.beta
2026-02-25 10:30:13 +00:00
Adrien Maston bab9d1d34a Merge pull request #2594 from basecamp/mobile/fix-card-background-image
Mobile / Fix card background image
2026-02-25 11:20:27 +01:00
Adrien Maston 7fa0504057 Merge pull request #2605 from basecamp/mobile/fix-clipped-stalled-bubble
Mobile / Fix clipped stalled bubble
2026-02-25 10:20:53 +01:00
Adrien Maston 3a9c8fdcb0 Merge pull request #2604 from basecamp/mobile/fix-flash-position
Mobile / Fix flash position
2026-02-25 10:20:20 +01:00
dependabot[bot] 31713aa396 Bump docker/build-push-action from 6.18.0 to 6.19.2 (#2587)
Bumps [docker/build-push-action](https://github.com/docker/build-push-action) from 6.18.0 to 6.19.2.
- [Release notes](https://github.com/docker/build-push-action/releases)
- [Commits](https://github.com/docker/build-push-action/compare/v6.18.0...v6.19.2)

---
updated-dependencies:
- dependency-name: docker/build-push-action
  dependency-version: 6.19.2
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-02-24 23:27:38 -08:00
Samuel Péchèr 0038641ede Update Lexxy to 0.7.6.beta 2026-02-24 20:25:07 +00:00
Adrien Maston 10c24b7734 Tweak translation 2026-02-24 17:48:24 +01:00
Adrien Maston 26282e3435 Use safe inset 2026-02-24 17:21:59 +01:00
Adrien Maston 88235a2737 Merge pull request #2602 from basecamp/fix-watching-overflow-dialog
Fix "watching" overflow dialog
2026-02-24 16:33:40 +01:00
Rosa Gutierrez 9ded80d448 Add test for notifying new mentionees when editing a comment
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-24 15:56:27 +01:00
Rosa Gutierrez 60b3c9c772 Fix duplicate push notification for mention and comment event
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>
2026-02-24 15:56:27 +01:00
Adrien Maston bd4cfbfd10 Change display on [open] dialog 2026-02-24 15:49:47 +01:00
Adrien Maston 7b93ec35b4 Merge pull request #2598 from basecamp/mobile/tweak-card-draft
Mobile / Tweak card draft
2026-02-24 14:56:31 +01:00
Denis Svara 3a14e3e338 Merge pull request #2599 from basecamp/mobile/path-config-setup
Mobile: Path config endpoint
2026-02-24 14:52:09 +01:00
Adrien Maston bf98b1a6d7 Adjust closed stamp position on perma 2026-02-24 12:44:13 +01:00
Denis Švara c0c78152c9 Add ios path config. 2026-02-24 11:26:07 +01:00
Denis Švara e00a51b6c2 Add android path config. 2026-02-24 11:26:00 +01:00
Denis Švara de3c8c46b5 Add constrained route. 2026-02-24 11:25:43 +01:00