1459 Commits

Author SHA1 Message Date
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
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
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
Denis Švara db9f67589c Implement ClientConfigurationsController for mobile client path config. 2026-02-24 11:25:07 +01:00
Stanko K.R. 0648583289 Fix other test failures caused by default host manipulation 2026-02-24 08:35:11 +01:00
Stanko K.R. 66fdcae960 Fix CI failure with no host set 2026-02-24 08:28:52 +01:00
Stanko K.R. 2f9d4533c5 Relativize absolute links to the instance on export 2026-02-24 08:22:01 +01:00
Stanko K.R. 4eadce8ffe Automatically update account slugs in links 2026-02-24 08:11:47 +01:00
Stanko K.R. 55779f8ac4 Organize the record sets better 2026-02-24 07:36:51 +01:00
Fernando 8d2075b653 Merge pull request #2580 from basecamp/fix-hyphenated-search
Fix hyphenated search
2026-02-20 12:14:38 -06:00
Rosa Gutierrez 3e5233239b Fix push notification not firing on notification creation
- 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
2026-02-20 18:37:33 +01:00
Fernando Olivares 85dc8ba455 Add repeated punctuation search stemmer test 2026-02-19 16:23:26 -06:00
Fernando Olivares 3932fce309 Add tests for hyphenated string search
Cover the stemmer tokenizing hyphenated input into separate words and
end-to-end search finding cards with hyphenated titles.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-19 16:16:44 -06:00
Adrien Maston c2d5070840 Merge pull request #2567 from basecamp/mobile/fix-empty-search 2026-02-19 14:28:54 +01:00
Adrien Maston 542777409c Add test 2026-02-19 12:07:08 +01:00
Jeremy Daer 953942694f Handle MissingEOCD as invalid zip file, not job failure (#2572)
ZipKit::FileReader raises MissingEOCD for truncated, corrupted, or
non-zip files. This exception is a direct StandardError subclass, not
a subclass of InvalidStructure or ReadError, so it escaped as an
unhandled job failure instead of being caught and surfaced to the user.

Broaden the rescue in ZipFile::Reader#initialize to catch ReadError
(parent of InvalidStructure), MissingEOCD, and UnsupportedFeature.
2026-02-18 15:18:56 -08:00
Mike Dalessio 41675224d0 Remove redundant insecure context CSRF check
Rails now handles the insecure context case natively in
verified_via_header_only? — when Sec-Fetch-Site is missing and the
request is plain HTTP without force_ssl, the request is allowed.

Remove the now-redundant allowed_insecure_context_request? method and
update the test to set ActionDispatch::Http::URL.secure_protocol
alongside Rails.configuration.force_ssl so the upstream check works
correctly in the test environment.
2026-02-18 14:06:46 -05:00
Alp Keser 056dfc1367 Merge pull request #2528 from basecamp/mobile/missing-boards-fix
Update boards JSON to return all accessible boards ordered by recently accessed
2026-02-18 13:20:49 +03:00
Alp Keser 610cf7a190 Merge pull request #2398 from basecamp/notifications-tray-api
Add JSON response support for `notifications/tray`
2026-02-18 13:19:59 +03:00
Kevin McConnell 4e9773f9f5 Correct card URL in comment JSON output
When rendering details of a comment, we were using the card's `id` as
the param for the URL. But card routes use the card's number, not ID.
2026-02-17 17:11:39 +00:00
Alp Keser 13d7cd4303 Paginate boards JSON 2026-02-17 10:37:23 +03:00
Rosa Gutierrez 95114b1d67 Fix race condition between concurrent Event and Mention notifier jobs
When concurrent NotifyRecipientsJobs process an Event and a Mention for
the same user+card, Rails' dirty tracking can skip writing source_type
in the UPDATE if it hasn't changed from the stale in-memory value,
leaving source_type and source_id mismatched (e.g. source_type='Event'
with a Mention's source_id, resulting in a nil source).

Force source_type to always be included in the UPDATE via
source_type_will_change! to prevent this.

Here's a sample timeline of this race condition happening in the real
world (with simplified IDs):

Two `NotifyRecipientsJob` involving notification `03fklpu`, same card,
same user, same comment — enqueued within 50ms of each other:

1. Both jobs load notification `03fklpu` — it has source_type='Mention'
   (from a previous job)
2. `EventNotifier` writes at 21:22:36.051: ```sql SET
   source_type='Event', source_id=<event_id>, unread_count=1 ```
3. `source_type` included because it changed ('Mention' → 'Event')
4. `MentionNotifier` writes at 21:22:36.057 (~6ms later): ```sql SET
   source_id=<mention_id>, unread_count=1 ```
5. No `source_type`! It was `'Mention'` when loaded and `'Mention'` is
   what it's setting → not dirty → skipped
6. Final DB state: `source_type='Event'` (from step 2, untouched),
   `source_id=<mention_id>` (from step 3)

`Notification.source` now does `Event.find(<mention_id>) → nil.`

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-16 16:50:46 +01:00
Alp Keser a48c88cb5d Stabilize notifications tray test ordering via fixture updated_at timestamps 2026-02-16 18:33:32 +03:00
Rosa Gutierrez b742963f7c Add account ownership check to import GID resolution
The convert_gids_to_sgids method resolved GIDs globally and minted
valid SGIDs without verifying the record belongs to the importing
account. This adds the same account_id check that the export path
already has, plus RecordNotFound handling for invalid GIDs.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-16 11:49:00 +01:00
Mike Dalessio dc492ef17e Merge pull request #2540 from basecamp/fix-quotes
Fix double-escaped HTML entities in basecamp and campfire webhook payloads
2026-02-13 09:26:00 -05:00
Mike Dalessio 0c10622672 Fix double-escaped HTML entities in webhook payloads
`Event::Description#to_plain_text` now returns an html-safe string to
prevent double-escaping when the ERB template renders the basecamp
and campfire payloads.

ref: https://3.basecamp.com/2914079/buckets/27/card_tables/cards/9574495379
2026-02-13 03:25:59 -05:00
Stanko K.R. 05042ed54a Fix crash when a notification is deleted while a bundle is being sent 2026-02-13 09:22:51 +01:00
Jirka Hutárek c4c46cce44 Add postponed and closed flags to Pinned and Notification cards 2026-02-13 00:45:47 +01:00
Mike Dalessio 4ead778e12 Backfill tests for Event::Description strings' html-safety 2026-02-12 15:52:56 -05:00
Mike Dalessio 851f13a934 Render inline code in card titles (#2518)
* Remove unused marked JS dependency

* Remove unused redcarpet dependency

* Render inline code in card titles

Add card_html_title helper that HTML-escapes input then converts
backtick-wrapped text to <code> elements. Apply to card titles in
board preview, card detail, public views, and notification emails.
Style inline code elements in titles to match description styling.

Co-authored-by: Andy Smith <andy@37signals.com>

---------

Co-authored-by: Andy Smith <andy@37signals.com>
2026-02-12 12:07:40 -05:00
Alp Keser a6eb331710 Fix query parameter name 2026-02-12 14:55:34 +03:00
Alp Keser 80546a6b47 Include read notifications in the tray API response 2026-02-12 14:55:34 +03:00
Denis Švara 3dca00a9ab Extend notification JSON payload.
Add avatar url, board name, and column.
2026-02-12 14:55:34 +03:00
Denis Švara 0b9869f3c9 Add JSON response support for notifications/tray. 2026-02-12 14:55:34 +03:00
Stanko K.R. 36ee253a1a Stack notifications everywhere
We had client-side notification stacking in the tray since launch, but now we want to stack notifications in the notifications page, in API responses and in email bundles.
2026-02-12 10:29:50 +01:00
Alp Keser 608be1f155 Fix boards JSON to return all accessible boards ordered by recently accessed. 2026-02-12 10:27:32 +03:00
Stanko K.R. 37d7f5c569 Give more detail about why the import failed 2026-02-11 13:47:21 +01:00
Mike Dalessio 8c5b47d774 Group notification emails by board (#2506)
* Group notification emails by board

Notification bundle emails now group items by board instead of
showing the board name redundantly for each card. Each board
section has a linked header and an <hr> separator.

* Adjust board title styles and remove rem units

* Sort board groups alphabetically

Sort notification email board sections alphabetically by name
(case-insensitive).

Also, rewrite mailer tests to use Nokogiri::HTML5 for precise DOM
assertions instead of regex matching.

---------

Co-authored-by: Andy Smith <andy@37signals.com>
2026-02-09 11:29:11 -05:00
Mike Dalessio d611b2cc6c Move comment events when card moves to a new board (#2486)
When a card moves between boards, comment events were staying on the
old board. Now they move with the card.
2026-02-04 15:04:28 -05:00
Mike Dalessio e311d8f521 Omit inactive users from board mention picker
The board-scoped mention prompt was returning all users with board access,
including those marked as inactive. This adds the `.active` scope to match
the behavior of the account-wide mention prompt.

Fixes https://app.fizzy.do/5986089/cards/3861
2026-02-03 18:42:15 -05:00
Mike Dalessio 7f473eda66 Validate ActionText associations during import check phase
ActionTextRichTextRecordSet now calls check_associations_dont_exist to
ensure imported ActionText records only reference records within the
same import, matching the behavior of the parent RecordSet class.
2026-02-03 13:46:28 -05:00
Mike Dalessio 015bc3420e Fix boost button appearing prematurely on draft cards
When updating a draft card (e.g., adding a background image), the turbo
stream response was using the published card container partial, which
includes the reactions/boost button. Draft cards should use the draft
container partial which intentionally omits reactions.

Fixes https://app.fizzy.do/5986089/cards/3970
2026-02-03 09:28:08 -05:00
Stanko K.R. a4bec6e407 Ignore missing attachments 2026-02-03 13:51:22 +01:00
Stanko K.R. 4b70c1cc3b Replace custom IO object with a transport manager IO 2026-02-02 19:31:18 +01:00
Stanko K.R. 77d9331d90 Add missing method to IO object 2026-02-02 19:06:37 +01:00
Stanko K.R. 5c929238b7 Rename jobs 2026-02-02 18:22:07 +01:00
Stanko K.R. b06d95a20d Delete accounts for failed imports 2026-02-02 16:24:10 +01:00
Stanko K.R. 96a1d7d2d3 Fix crash on successful import email 2026-02-02 14:47:58 +01:00
Stanko K.R. a6609e1bbc Add test got the import job 2026-02-02 13:03:26 +01:00