Commit Graph

759 Commits

Author SHA1 Message Date
Mike Dalessio b6ea558de8 Move TrackTrueClientIp middleware into saas engine (#2677)
The True-Client-IP header is set by Cloudflare and is only trustworthy
when behind a Cloudflare proxy. In non-Cloudflare deployments, this
header is attacker-controlled and can be used to spoof IP addresses.

Moving the middleware into the saas engine ensures it only loads for our
Cloudflare-fronted production deployment, not for self-hosted OSS
instances.

GHSA-cpch-9qg2-x8fq
2026-03-09 15:14:17 -04: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 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 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
Denis Švara de3c8c46b5 Add constrained route. 2026-02-24 11:25:43 +01:00
dependabot[bot] 0ead67f85b Bump the development-dependencies group across 1 directory with 3 updates (#2546)
* Bump the development-dependencies group across 1 directory with 3 updates

Bumps the development-dependencies group with 3 updates in the / directory: [brakeman](https://github.com/presidentbeef/brakeman), [faker](https://github.com/faker-ruby/faker) and [selenium-webdriver](https://github.com/SeleniumHQ/selenium).


Updates `brakeman` from 7.1.2 to 8.0.1
- [Release notes](https://github.com/presidentbeef/brakeman/releases)
- [Changelog](https://github.com/presidentbeef/brakeman/blob/main/CHANGES.md)
- [Commits](https://github.com/presidentbeef/brakeman/compare/v7.1.2...v8.0.1)

Updates `faker` from 3.5.3 to 3.6.0
- [Release notes](https://github.com/faker-ruby/faker/releases)
- [Changelog](https://github.com/faker-ruby/faker/blob/main/CHANGELOG.md)
- [Commits](https://github.com/faker-ruby/faker/compare/v3.5.3...v3.6.0)

Updates `selenium-webdriver` from 4.39.0 to 4.40.0
- [Release notes](https://github.com/SeleniumHQ/selenium/releases)
- [Changelog](https://github.com/SeleniumHQ/selenium/blob/trunk/rb/CHANGES)
- [Commits](https://github.com/SeleniumHQ/selenium/compare/selenium-4.39.0...selenium-4.40.0)

---
updated-dependencies:
- dependency-name: brakeman
  dependency-version: 8.0.1
  dependency-type: direct:development
  update-type: version-update:semver-major
  dependency-group: development-dependencies
- dependency-name: faker
  dependency-version: 3.6.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: development-dependencies
- dependency-name: selenium-webdriver
  dependency-version: 4.40.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: development-dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>

* Auto-sync Gemfile.saas.lock on Dependabot PRs

Dependabot can't update custom-named Gemfiles, so Gemfile.saas.lock
drifts whenever it bumps shared gems in Gemfile.lock.

Add `bin/bundle-drift forward` to push oss lockfile changes into the
saas lockfile (the reverse of `correct`), and a GitHub Actions workflow
that runs it automatically on Dependabot bundler branches.

* Update brakeman ignore fingerprint for 8.0.1

Brakeman 8.0.1 changed how it computes warning fingerprints, so the
existing ignore entry for the safe_constantize warning in Notifier
became obsolete. Update to the new fingerprint.

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Jeremy Daer <jeremy@37signals.com>
2026-02-16 15:29:51 -08:00
Jeremy Daer bc76633f77 Reduce webhook delivery cleanup impact (#2550)
* Add index on webhook_deliveries.created_at

The stale scope (WHERE created_at < ?) and ordered scope
(ORDER BY created_at DESC) both benefit from this index.
Without it, Webhook::Delivery.cleanup does a full table scan.

* Run webhook delivery cleanup every 15 minutes

Each run now deletes ~15 minutes of newly-stale records instead
of ~4 hours worth, a 16x reduction in per-run write volume.
2026-02-16 14:28:05 -08: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
Rosa Gutierrez 692fd73512 Run only Solid Queue's maintenance tasks in staging
And not all production tasks, because that causes unexpected side
effects such as cards being auto-postponed and firing webhooks
configured to trigger on that event.
2026-02-09 13:19:12 +01:00
Stanko K.R. 5dac2fa678 Finishing touches 2026-02-02 18:06:19 +01:00
Stanko K.R. 6e6e4e9b5a Fix orphaned attachments when deleting the whole account 2026-02-02 14:35:01 +01:00
Stanko K.R. 8be26a283e Tell brakeman that everything is OK 2026-02-02 13:39:54 +01:00
Stanko K.R. 58a14fb1f5 Fix load error 2026-02-02 12:49:39 +01:00
Stanko K.R. de69d2e6fe Add import cleanup 2026-02-02 12:36:48 +01:00
Stanko K.R. 635dbb90cb Rename the Import controller to Account::Import 2026-02-02 12:36:48 +01:00
Stanko K.R. 19ae555c2e Convert Account::SingleUserExport to User::DataExport 2026-02-02 12:36:48 +01:00
Stanko K.R. 161efb0b8e Implement basic imports 2026-02-02 12:36:48 +01:00
Kevin McConnell 108337e823 Cache public pages for 5 mins, not 1 year
Given these may be cached in CDNs, and they aren't versioned, a shorter
expiration time makes it practical to change them.
2026-01-28 14:30:43 +00:00
Kevin McConnell e105ca6fb5 Remove zero-padding from account slug
This shortens URLs in the self-hosted case, where external account ID is
typically a small number.
2026-01-28 11:59:42 +00:00
Mike Dalessio cb61b36715 Allow boosts on cards (#2411)
* Add reactions association to Card model

Adds `has_many :reactions` to Card, matching the implementation in Comment.
This allows cards to be reacted to directly with emoji reactions.

The association:
- Orders reactions chronologically
- Uses polymorphic `:reactable` interface
- Deletes reactions when card is destroyed

Includes test coverage for the new association.

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

* Add reactions UI to card detail view

- Create Cards::ReactionsController with turbo stream responses
- Add card reactions views (reactions list, new form, menu partial)
- Position reaction button flush-right when no reactions exist
- Show reactions on bottom-left when present, with button inline
- Handle narrow viewports by flowing button below meta section
- Add controller tests for card reactions

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

* Add boost count to card preview

Display reaction count alongside comment count on card tiles in board
columns. Introduces a .card__counts wrapper to group both counts with
proper positioning on all viewport sizes.

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

* Consolidate reaction views for cards and comments

Extract shared views to app/views/reactions/ using polymorphic routing.
Both card and comment reactions now render the same partials, with a
helper to compute the correct path prefix for nested routes.

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

* Fix emoji picker width in card reactions

Override .card .popup { inline-size: 260px } for the reaction popup
so the emoji grid displays without horizontal scrollbar.

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

* Hide boost count where comment count is hidden

Add .card__boosts alongside .card__comments in:
- Tray view (display: none)
- Cards with background images (opacity toggle on hover)

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

* Fix reactions button overlap with zoom button in card footer

When a card has a background image, the footer contains both a reactions
button and a zoom button. Previously they would overlap because the
reactions button was absolutely positioned to the bottom-right.

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

* Tighten up previews

* Move card reactions from meta partial to container

Fixes two issues:

1. Reactions were duplicated each time a card was assigned because the
   turbo stream replaced the meta div with the full perma/meta partial,
   which included reactions outside the replaced element.

2. Draft cards incorrectly showed the boost button because the meta
   partial was shared between published and draft containers.

Moving reactions to _container.html.erb (published cards only) fixes
both issues and keeps the meta partial focused on meta content.

Fixes https://app.fizzy.do/5986089/cards/3837
Fixes https://app.fizzy.do/5986089/cards/3835

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

* WIP adjust perma page

* Position and style the footer

* Render the stamp in the card body instead of the footer

* Fix undefined local variable in public cards view

Changed `card` to `@card` when rendering the stamp partial.

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

* Preload card reactions to avoid N+1 queries

Include reactions and their reacters in the preloaded scope, matching
what we already do for comments.

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

* Add model tests for card and comment reaction cleanup

Test that:
- Creating a card reaction touches the card's last_active_at
- Reactions are deleted when their parent comment is destroyed
- Reactions are deleted when their parent card is destroyed

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

* Minor improvements to reactions code

- Use size instead of count in boosts partial to avoid extra SQL query
  on already-loaded collection
- Add else clause to reaction_path_prefix_for to raise ArgumentError
  for unknown reactable types instead of silently returning nil

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

* Bump specificity of reaction popup in the card perma

---------

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Co-authored-by: Andy Smith <andy@37signals.com>
2026-01-23 13:25:55 -05:00
Kevin McConnell 23fe40fa81 Correctly initialise WebPush connection (#2417)
The `ipaddr` arg here is being interpreted as a positional arg (since
the keyword arg doesn't exist), which results in an invalid connection
object. This was causing push notifications to silently fail.

We should initialise the property on the object instead.
2026-01-22 12:03:02 -08:00
Jay Ohms a9b873f151 Merge branch 'main' into mobile/bridge-components
* main:
  Create popup initial alignment classes
  Fix class typo
  Hide the board selector button if card is closed
  Align board and tag picker dialogs without using math
  Add dialog manager to card perma
  Fix "M" hotkey using stale user from fragment cache
  Add Enable all/Disable all buttons to webhook event selection
  Remove whitespace
  Remove hover styles from disabled menu items

# Conflicts:
#	app/views/cards/_container.html.erb
2026-01-19 12:02:42 -05:00
Mike Dalessio 2527f07365 Fix "M" hotkey using stale user from fragment cache
Add a SelfAssignmentsController to resolve the current user at request
time, avoiding the use of an incorrect user id baked into a cached URL.

ref: #2211 originally added the "M" hotkey
ref: https://app.fizzy.do/5986089/cards/3722
2026-01-16 13:41:33 -05:00
Jay Ohms 3cada3ce82 Merge branch 'main' into mobile/bridge-components
* main:
  Add rollback script to convert relative URLs back to absolute
  Add a script to migrate existing rich texts to relative URLs
  Use relative URLs where possible across all the app
  Use relative URLs for avatars and rich text attachments
2026-01-14 09:39:08 -05:00
Rosa Gutierrez 235890e666 Use relative URLs for avatars and rich text attachments
These URLs can be problematic or inconsistent, as we might end up with
rich text content with beta URLs for attachments being used in
production, or viceversa. It'll also be problematic when importing or
exporting data between a self-hosted instance and the SaaS version.
2026-01-14 11:01:42 +01:00
Adrien Maston 3b97b2e5c8 Merge branch 'main' into mobile/bridge-components
* main: (22 commits)
  Disable card column buttons when active
  Prevent comment content from overlapping with reaction button
  Delete pins belonging to cards in a board with revoked access
  Update development dependencies in SAAS lockfile
  Update runtime dependencies
  Update development dependencies
  Balance magic link instructions
  Add `.txt-balance` utility
  Move handleDesktop inside restoreColumnsDisablingTransitions
  Adjust border radius
  Clean up blank slates
  Add dialog manager to events page
  More sturated mini bubbles in light mode
  Brighten mini bubbles in dark mode
  Use separate cache namespaces for each beta instance
  Bump boost size up a tick on mobile
  Move Undo form inside closure message element
  Phrasing tweak for exceeding storage limit
  Keep strong tags words on same line
  Allow ActionText embed reuse and safe purge (#2346)
  ...
2026-01-14 10:34:13 +01:00
Rosa Gutierrez 025d70dd13 Use separate cache namespaces for each beta instance
Otherwise we end up with views including URLs from all the betas,
leading to some confusion and problems with the CSP.
2026-01-12 20:28:18 +01:00
Jeremy Daer 006e61ea58 Allow ActionText embed reuse and safe purge (#2346) 2026-01-12 10:30:18 -08:00
Adrien Maston 6fab121f51 Merge branch 'main' into mobile/bridge-components
* main:
  Remove explicit queue definition
  Fix attribute name
  Explicitly define incineration priority
  Fix incorrect recurring job config
  Add self-service account deletion (#2246)
  Document DELETE endpoint for removing card header image
  Document goldness endpoints for marking cards as golden
  Document closed and column fields in card API response
2026-01-12 17:33:29 +01:00
Stanko K.R. 7c7b4eb621 Remove explicit queue definition 2026-01-12 15:16:33 +01:00
Stanko K.R. 5f11ee154f Fix attribute name 2026-01-12 15:16:33 +01:00
Stanko K.R. 74c7ed3081 Explicitly define incineration priority 2026-01-12 15:16:33 +01:00
Stanko K.R. ae78f25b06 Fix incorrect recurring job config 2026-01-12 15:16:33 +01:00
Stanko Krtalić eceb6c9b43 Add self-service account deletion (#2246)
* Add self-service account deletion

* Disable access to cancelled accounts & implement Stripe interactions

* Add tests

* Fix failing tests

* Remove cancelled accounts from lists

* Fix incorrect redirect

* Use _path instead of _url for consistency

* Don't track how far the inicieration job got

We still want the step tracking so that the job can be interrupted. But, since the scope is idempotent, we don't need to track how far it got.

* Specify the exact time when the data will be deleted

* Fix crash due to unadvancable cursor

* Rename up_for_incineration to due_for_incineration

* Regenrate the schema

* Fix incorrect path check

* Migrate the SQLite schema

* Only show the cancel button on cancellable accounts

* Check that a subscirption method exists before calling it

* Ignore job failures due to missing records when an account gets deleted

* Skip sending notifications on cancelled accounts

* Use collbacks to integrate

* Add a blank line between queue_as and discard_on

* Break checks into methods

* Inline methods

* Rename Account::IncinerateJob

* Run migrations

* Migrate SQLite
2026-01-12 14:21:05 +01:00
Jay Ohms ac45da2b99 Merge branch 'main' into mobile/bridge-components
* main:
  Fix that caching should be disabled in dev by default. References 3cf841d463.
  Revert "Add CJK (Chinese, Japanese, Korean) search support"
  Account for new button sizes on mobile
  Bump filter z-index when filters are open
  Use a dedicated resource for showing cards in draft mode
  Refactor popup.css styles for focus state
  Add Segoe UI Variable Fizzy font face configuration comments
  Update @layer name to `base` in font-face definition for "Segoe UI Variable Fizzy"
  Fix font-face `src` reference for "Segoe UI Variable Fizzy" to correct local font lookup
  Adjust font-weight range for "Segoe UI Variable Fizzy" to include 800-900
  Update --font-sans and related font-face references to "Segoe UI Variable Fizzy" for consistency
  Add "Segoe UI fizzy" to --font-sans and define custom font faces
  Update --font-sans to include "Segoe UI Variable" for improved text rendering
  Add margin to details inside popup styles
2026-01-08 15:46:29 -05:00
Jeremy Daer d913ef5b6a Fix that caching should be disabled in dev by default. References 3cf841d463. 2026-01-08 12:26:18 -08:00
Denis Švara eae17b82b4 Merge branch 'main' into mobile/bridge-components
* main: (65 commits)
  Repair scope for card styles within the columns view
  Add missing list to cards page
  Account for simpler public views
  Fix public layout
  Pull board-tools outside of the list for mobile
  saas: move log_level setting into an environment variable
  Restore log level configurability in production environment
  Remove engagements
  Use notch class so Save button is positioned correctly
  Go back to board after clearing filters
  Revert "Use existing no_filtering_url to direct back to the board when clearing filters"
  Use existing no_filtering_url to direct back to the board when clearing filters
  Ensure filters sit on top of cards
  Fix public boards
  Fix card grid layout
  Fix Chinese/Japanese characters missing from printed PDFs on macOS
  Add SQLite FTS5 support for CJK search
  Add CJK (Chinese, Japanese, Korean) search support
  Fix scrolling on mobile
  Remember expanded state on navigation
  ...
2026-01-08 14:34:10 +01:00
Jorge Manrubia f58a1aeb31 Use a dedicated resource for showing cards in draft mode
Mobile team needs this. Also, this lets us get rid from some conditionals for handling
both modes with a single template.

https://3.basecamp.com/2914079/buckets/44843469/messages/9437287330
2026-01-08 10:40:21 +01:00
Dmytro Shteflyuk 0417639049 Restore log level configurability in production environment 2026-01-07 11:24:22 -05:00
Denis Švara b84da94016 Add Hotwire Native Bridge wiring. 2026-01-06 15:46:47 +01:00
Jay Ohms ea43d81850 Support mobile app local development access from devices over nip.io 2026-01-05 09:31:27 -05:00
Mike Dalessio 96dfcb9d4c Add a "*" to the queues handled by the solid queue worker pool
Specifically this is to make sure all queues are managed, like the one
we use for incineration in the fizzy-saas engine.
2026-01-04 14:09:42 -05:00
Mike Dalessio 27093e4e78 Remove unnecessary recurring execution cleanup task
This task is unnecessary because the
`solid_queue_recurring_executions` table has a foreign key to
`solid_queue_jobs` with `on_delete: :cascade`.

This config was cargo-culted from HEY (in 44429644), which *does* need
this task to run because we've omitted the FK constraints there.
2026-01-04 11:12:01 -05:00
Rosa Gutierrez 196d685f8d Implement authorization for Active Storage endpoints
Consider blobs attached to any public records accessible to anyone with
the URL.
2025-12-25 21:22:36 +01:00
Rosa Gutierrez 329e9ef7cb Don't run application recurring jobs in betas
Betas use the same application DB as production, so all these tasks
already take place there. Moreover, since betas have different Solid
Queue instances, we can't prevent simultaneous runs of the same
recurring jobs because each Solid Queue instance is isolated from the
others, so they don't know these jobs are running already, leading to
all sort of issues.
2025-12-23 22:12:08 +01:00
Rosa Gutierrez 3fcf5a9d08 Require authorization for direct uploads
Respond with 403 to JSON requests that are unauthorized, instead of
redirecting.
2025-12-23 11:08:51 +01:00
Jeremy Daer bd6c1cf34f Storage: harden reconcile for concurrent writes and fix board transfer (#2096)
* Storage: harden reconcile for concurrent writes and fix board transfer

Reconcile now uses two-cursor approach: captures cursor before and after
the storage scan, aborting if they differ (entries added during scan).
Job retries 3x with 1-minute waits and limits concurrency to 1 per owner.

Board transfer now correctly moves storage for card description embeds
and comment embeds, not just direct attachments. Uses batched queries
to handle cards with thousands of comments efficiently.

Also fixes N+1 queries in attachment grouping via lookup maps.

* Storage: fix per-attachment reconcile and enforce no blob reuse

The storage ledger tracks per-attachment (not per-blob) as a business
abstraction for quotas. This fixes reconcile to match that model and
adds enforcement to prevent blob reuse in tracked contexts.

* Reconcile now uses joins(:blob).sum() for per-attachment counting
* New validation prevents reusing blobs across tracked attachments
* Race-safe storage_total creation with create_or_find_by
* Job efficiency: skip find_by when object already available
* Backfill script checks per-attachment, not just per-blob
2025-12-19 13:07:32 -08:00
Kevin McConnell 50691ae385 Merge pull request #2205 from basecamp/base-url-env
Add `BASE_URL` environment variable
2025-12-19 14:58:08 +00:00
Mike Dalessio b04dee31d0 Raise a clear exception if libvips is not installed 2025-12-19 09:22:55 -05:00
Mike Dalessio 28250b340c Move the :fatal log setting into the SaaS config
This should only be set if the app is using the
rails-structured-logging gem, which means it should only be set in the
SAAS config. I think this is just something we missed in one of the
decoupling exercises we did.
2025-12-19 09:09:40 -05:00