Commit Graph

6758 Commits

Author SHA1 Message Date
Mike Dalessio 3a30d7796f style: formalize preference for if/unless in rubocop (#2816) 2026-04-09 11:14:52 -04:00
Rob Zolkos 5e154a767f Allow bearer auth on Active Storage downloads 2026-04-08 14:37:24 -04:00
Zoltán Hosszú 0ef9c56bf5 Merge pull request #2811 from basecamp/lexxy-toolbar-fixes
Fix icon color and chevron size
2026-04-08 15:13:15 +02:00
Rob Zolkos 1975a04f14 Add JSON API support for email address changes (#2807) 2026-04-08 09:09:36 -04:00
Zoltan Hosszu d7040388fa Fix icon color and chevron size 2026-04-08 15:09:29 +02:00
Rob Zolkos d08aa11bfb Add JSON API support for timezone update (#2806)
* Add JSON API support for timezone update

* Simplify timezone controller to just head :no_content

* Use PUT instead of PATCH in timezone tests to match docs

* Use PATCH instead of PUT for timezone endpoint

Reverts tests to use patch and updates docs heading to PATCH
for consistency.
2026-04-08 08:41:34 -04:00
Rob Zolkos 6a71856b3d Add JSON activities API endpoint (#2783)
* Add JSON events API endpoint

* Add regression test for event particulars defaults

* Move JSON events API to a dedicated ActivitiesController

The events endpoint served both the HTML day timeline and the JSON API
feed, but the two paths shared no data or behavior — the HTML side uses
DayTimelinesScoped while the JSON side built its own query. Splitting
into ActivitiesController gives the API its own home at
GET /:account/activities.json without dragging in the timeline
before_actions.

Also preloads comment creator in Event.preloaded to avoid an N+1 when
rendering comment eventables in the JSON feed.
2026-04-08 08:39:36 -04:00
Rob Zolkos 2a4adf3a2b Add board settings fields to board JSON responses (#2788)
* Add board settings fields to board JSON responses

* Move public_description fields to board partial, gate on published

* Add index test for public_description fields on published boards

* Update app/views/boards/show.json.jbuilder

Prefer `unless` to `if !`

---------

Co-authored-by: Mike Dalessio <mike.dalessio@gmail.com>
2026-04-08 08:31:52 -04:00
Rob Zolkos 8201c2a4bd Add JSON API support for user data exports (#2786) 2026-04-08 08:17:24 -04:00
Rob Zolkos 21981898d2 Add JSON API for webhook deliveries (#2785)
* Add JSON API for webhook deliveries

* Extract sanitized_request method on Webhook::Delivery

* Extract response_summary method on Webhook::Delivery
2026-04-07 15:08:27 -04:00
Donal McBreen e51f0bfee7 Fix account destroy cascade gaps
Account incineration could leave orphaned records due to missing cascade
declarations and async jobs failing when the account was already gone.

Cascade fixes:
- Add Account::Searchable concern to clean up Search::Query (delete_all)
  and Search::Record (destroy_all, respects SQLite FTS dependent: :destroy)
- Add before_destroy in Account::Storage to delete storage entries
- Suppress storage entry recording during incineration so attachment
  purge callbacks don't create entries or enqueue materialize jobs
- Guard Access#clean_inaccessible_data_later with unless user.destroyed?
  to avoid enqueuing pointless jobs during user cascade

Job tenancy:
- Extract AccountTenanted concern from the global ActiveJob initializer
  into ApplicationJob (include) with targeted prepends for
  ActionMailer::MailDeliveryJob and Turbo broadcast jobs
- Defer account resolution from deserialize to perform so that missing
  accounts raise DeserializationError inside the execution path where
  discard_on can handle it

Tests:
- Comprehensive incineration test covering 35 model types with
  before/after assertions and full enqueued job processing
- Mailer deliver_later test verifying account context survives job
  serialization for multi-account users
- Turbo broadcast test verifying account-scoped URLs in rendered partials
2026-04-06 10:04:24 +01:00
Donal McBreen 6d71385846 Fix timezone handling for day timeline and card dates
Revert beginningOfDay() to use local timezone methods — the UTC change
from #2790 caused cards created earlier the same day to show "yesterday"
for users in UTC-negative timezones after their local 7 PM.

Add Time.zone.name to day timeline fragment cache keys so
timezone-different renders don't collide.
2026-04-06 09:21:03 +01:00
Donal McBreen b79c7897e6 Fix export/import losing custom column colors
record.to_json serializes through model accessors. Column::Colored
overrides the color accessor to return a Color struct, which serializes
as {"name":"Blue","value":"var(--color-card-default)"} instead of the
raw CSS string.

Two fixes:
- Export raw DB values via record.attributes.slice(*attributes).to_json
- Color.for_value parses legacy JSON format so columns imported from
  old exports self-heal when read
2026-04-03 10:05:11 +01:00
Donal McBreen c2d693fc9a Fix off-by-one date display for UTC-negative timezone users
beginningOfDay() used local timezone methods (getFullYear, getMonth,
getDate) to snap timestamps to midnight. Since the server sends UTC
timestamps, this caused day boundary calculations to be off by one
for users in UTC-negative timezones — cards created after UTC midnight
but before local midnight showed as "yesterday" instead of "today".

Switch to UTC methods (getUTCFullYear, getUTCMonth, getUTCDate) so day
boundaries are calculated consistently with the server's UTC timestamps.

Affects DaysAgoFormatter, DaysAgoOrWeekdayFormatter, TimeOrDateFormatter,
InDaysFormatter, and bubble controller stalled/entropy calculations.
2026-04-03 08:50:25 +01:00
Donal McBreen b4c4a32205 Return 401 for bearer tokens on non-JSON requests
Rather than silently ignoring bearer tokens on HTML requests
(which breaks the audit console's error handling), explicitly
reject them with 401. Bearer tokens on JSON requests continue
to authenticate normally.
2026-04-01 09:09:00 +01:00
Donal McBreen 13e2c74929 Restrict bearer token authentication to JSON requests
Bearer tokens are for API/SDK/CLI access — they should only
authenticate JSON requests.
2026-04-01 08:55:04 +01:00
Donal McBreen d63801eae3 Merge pull request #2775 from basecamp/queenbee-account-callbacks
Implement Queenbee account callbacks
2026-04-01 08:09:25 +01:00
Donal McBreen 98d06947cb Implement Queenbee account callbacks
Add Account::QueenbeeIntegration concern (SaaS-only) that bridges
Fizzy's external_account_id to Queenbee's find_by_queenbee_id lookup,
and adapts the bang lifecycle methods to Fizzy's Cancellable module.

Remove dead billing conditional from cancellation mailer — Fizzy is
free-only, there's no charge to stop.
2026-03-31 15:14:49 +01:00
Jay Ohms e04182786c Merge pull request #2762 from basecamp/mobile/hide-profile-links
Mobile: hide profile links in native apps
2026-03-31 09:42:55 -04:00
Adrien Maston a5cc2e8ab5 Change CSS selector 2026-03-30 16:38:32 +02:00
Adrien Maston f11bb18d93 Tweak alignments 2026-03-30 13:53:14 +02:00
Stanko K.R. c2fe41d376 Fix sign in flow 2026-03-27 10:13:57 +01:00
Jay Ohms 3ccfb864db Hide the links to manage passkeys and sign out from the webview in the native apps 2026-03-27 04:41:43 -04:00
Stanko K.R. 76d00d17c8 Abort conditional mediation when the button disconnects 2026-03-26 16:49:59 +01:00
Stanko K.R. 9614c1ecfb Add a button to sign in with a passkey 2026-03-26 15:59:09 +01:00
Stanko Krtalić 09d5b8c92f Merge pull request #2756 from basecamp/friendly-error-message-on-duplicate-passkey
Show separate error message when adding a Passkey twice
2026-03-26 14:19:35 +01:00
Stanko K.R. 6107d3cee1 Add margin ot error messages 2026-03-25 18:16:11 +01:00
Stanko K.R. 01d66a9463 Change challange expiration based on purpose 2026-03-25 17:31:42 +01:00
Stanko K.R. 693415bc71 Rename request options to authentication options 2026-03-25 17:26:49 +01:00
Stanko K.R. df7c2678f3 Rename create_options to registration_options 2026-03-25 17:26:49 +01:00
Stanko K.R. 8e4f9ce5e7 Cleanup form helpers 2026-03-25 17:26:49 +01:00
Stanko K.R. 4fb1cddb4c Use WebComponents for buttons
This simplifies the loading and cleanup logic, while providing ubiquitous support across JS frameworks. Buttons can now be added in any way imaginable and still work without requiring additional initialization. The upside of this aproach is that it doesn't require a mutation observer nor a global click listener, and is supported by all browsers that also support passkeys.
2026-03-25 17:26:49 +01:00
Sean Mitchell 4b4454de89 Update colophon icons 2026-03-24 13:32:39 -07:00
Jason Fried 2c60ddb7c7 Added Basecamp + HEY icons/links 2026-03-24 12:12:33 -07:00
Stanko K.R. 134a533e28 Fix Passkey buttons not triggering sometimes 2026-03-24 14:36:52 +01:00
Andy Smith 7632802f18 Merge pull request #2682 from nqst/delete-account-improvements
Delete account: design improvements
2026-03-23 09:38:23 -05:00
Stanko K.R. 49fbb82676 Permit variant records in imports 2026-03-23 12:30:40 +01:00
Zacharias Knudsen 6808250c8f Merge pull request #2745 from basecamp/excert-truncation
Pre-truncate excerpt before running replacement regexes
2026-03-23 10:24:58 +01:00
Zacharias Dyna Knudsen 6c771584d7 Pre-truncate excerpt before running replacement regexes
Also fixes <ol> normalization which was previously a noop.
2026-03-23 10:21:35 +01:00
Mike Dalessio 95a45aa651 Don't memoize AutoLinkScrubber across format_html calls
The scrubber was memoized on the view helper in b0fa6525 so that
@regexp_timeout_reported would stop scanning remaining text nodes
after a timeout. But memoizing on the helper leaks that flag across
all format_html calls for the entire request — if one comment triggers
a timeout, every subsequent comment on the page loses auto-linking.

Use a fresh scrubber per call. The @regexp_timeout_reported flag still
works within a single document's scrub, which is the intended scope.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-20 16:03:47 -04:00
Zacharias Dyna Knudsen b0fa6525fe Stop scrubbing after first regexp timeout 2026-03-20 12:13:17 +01:00
Alexander Zaytsev 6d6f94ce13 Merge branch 'main' into delete-account-improvements 2026-03-19 15:26:31 +01:00
Alexander Zaytsev 754e2e3673 Cancel → Delete 2026-03-19 15:06:37 +01:00
Jorge Manrubia cf10dbd91e Add 1GB storage cap with staff bypass (#2729)
Re-applies the storage limit from #2713 (reverted in #2715) with an
exception for staff identities so our own account isn't blocked.
2026-03-19 12:38:39 +01:00
Mike Dalessio 11df9c3589 Fix back navigation from activity page to cards (#2727)
Three changes needed to support navigating back from a card to the
activity page:

- Add root_path to the prefer_referrer allowlist on the card show page
- Switch event links from HTML target="_top" to data-turbo-frame="_top"
  so Turbo handles the navigation and turbo:before-visit fires to save
  the referrer
- Normalize trailing slashes in the referrer path comparison so
  /account_id and /account_id/ both match

ref: https://app.fizzy.do/5986089/cards/2390
2026-03-18 22:03:36 -04:00
Mike Dalessio 248fc5d3e7 Fix back navigation from filter views to cards (#2725)
* test: update sign_in_as system test helper to be more generic

and support fixture users with more than one board

* test: backfill a test for the card "back link"

when coming from a board filter page

* Fix back navigation to filter views from cards

When navigating from a filter view to a card, the back link now shows
the filter's label and navigates back to the filter URL instead of the
card's board.

The turbo-navigation Stimulus controller stores the page title alongside
the referrer URL in sessionStorage. On the card show page, a
referrerBackLink target rewrites the back link's href and label from the
stored values. This is scoped only to the card show page to avoid
navigation loops in multi-level deep pages.

* Add referrer allowlist to prevent stale referrer rewrites

The back link is only rewritten when the stored referrer's path matches
an allowlist of paths passed via prefer_referrer. This prevents stale
referrers from unrelated pages (e.g., settings) from overriding the
card's back link.

* Move back link navigation tests to dedicated system test file

Extract back link tests from smoke_test.rb into their own file since
they test specific JavaScript behavior that needs regression coverage.
Move sign_in_as helper to ApplicationSystemTestCase for reuse.

* Move markdown paste tests to dedicated system test file
2026-03-18 17:03:13 -04:00
Daniel Pinto ad1b10368d Fix export size doubling from re-exporting prior export blobs (#2707)
* Exclude export/import blobs and attachments from account exports

Export and import file blobs were being assigned the account's
account_id (via the Current.account default), causing subsequent
exports to include previous export/import ZIP files in the data.
This roughly doubled the export size each time and made imports
fail with an IntegrityError on unrecognized "Account::Export"
record type.

Filter out blobs attached to Export/Import records in
BlobRecordSet, FileRecordSet, and a new AttachmentRecordSet.

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

* Use STI base type for export blob exclusion filter

Rails polymorphic_name returns base_class.name for STI models,
so ActiveStorage stores record_type as "Export" rather than the
concrete subclass names. Filter on the base type accordingly.

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

* Fix export filtering for mixed-use Active Storage blobs

Exclude only internal-only blobs for account export metadata/file sets, scope attachment subqueries by account, and add regression coverage for blobs shared between user records and export attachments.

* Simplify export blob filtering

Exports always create fresh blobs, so a blob can never be legitimately
shared between an Export/Import and a real account record. Replace the
internal_only/external split with a single excluded_blob_ids subquery
and drop the contrived shared-blob test.

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

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-18 11:41:22 -04:00
Peter Baker e0d66b2fd0 Fix ambiguous column in SQLite FTS multi-term search (#2688)
* Fix multi-term SQLite FTS5 search causing a 500 error

When filtering cards by more than one term, `matching` was called
once per term via an association join. Rails did not deduplicate
association joins, resulting in two JOINs to `search_records_fts`
in a single query. SQLite FTS5 then raised "ambiguous column name"
when evaluating the MATCH condition.

Fix by using a string join instead, which Rails deduplicates so
only one JOIN to `search_records_fts` is generated regardless of
how many terms are searched.

Fixes: https://github.com/basecamp/fizzy/discussions/2354

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* test: verify multi-term search requires ALL terms to match

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: Mike Dalessio <mike@37signals.com>
2026-03-18 11:38:06 -04:00
Stanko Krtalić 5701aad8d7 Merge pull request #2623 from basecamp/passkeys
Passkeys
2026-03-18 14:54:54 +01:00
Stanko K.R. 14f938dbf9 Add demo video for creating a Passkey 2026-03-18 14:48:00 +01:00