Commit Graph

690 Commits

Author SHA1 Message Date
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 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
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. 27c73ff42b Rename validate to check
Validate is an ActiveModel method, overriding it could have unintended side-effects so I opted to rename it
2026-02-02 12:36:48 +01:00
Stanko K.R. 992f15066b Replace RubyZip with ZipKit
ZipKit can read and write files directly from S3 which makes both imports and exports way more efficient in terms of disk usage.
2026-02-02 12:36:48 +01:00
Stanko K.R. 184150006f Unify ZIP file interactions between all exports 2026-02-02 12:36:48 +01:00
Stanko K.R. e0cee2c770 Simplify the import test 2026-02-02 12:36:48 +01:00
Stanko K.R. f50d6d093d Implement cursors for imports 2026-02-02 12:36:48 +01:00
Stanko K.R. 63f6be4ef5 Resolve import conflicts 2026-02-02 12:36:48 +01:00
Stanko K.R. b97934ff48 Fix crash when exporting ActiveStorage files 2026-02-02 12:36:48 +01:00
Stanko K.R. 00c5f7feab Break import and export objects into record sets 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
Stanko K.R. 6f17e3867d Split Export model into user and whole-account export 2026-02-02 12:36:31 +01: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
Mike Dalessio 0d138df952 Guard search indexing with searchable? check
Only index cards and comments if their card is `published?`, using a
new method `searchable?` that is implemented on both Card and Comment.
This prevents draft cards and their comments from being indexed.

When drafts are published, the existing `update_in_search_index`
callback creates the record via `upsert!`, or else ensures unpublished
records are removed from the index.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-23 12:13:08 -05:00
Mike Dalessio 9aa15e9fb2 Forbid comments on draft cards
- Add Card#commentable? method that returns true only for published cards.
- CardsController#create ensures that the card is commentable.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-23 11:28:26 -05:00
Mike Dalessio bb2d3a59b5 prefactor: update search to use published cards
We're about to make a change to assert that draft cards are not added
to the search index, and so let's make the intention behind these
tests clear first.
2026-01-23 11:07:43 -05:00
Mike Dalessio 1aea6850f0 Extract Card::Commentable 2026-01-23 10:30:12 -05:00
Kevin McConnell 468fe5f16b Only index up to 32KB of search content
To prevent overflowing the columns in the search index tables (and also
just to avoid creating massive indexes), let's limit the searchable
content to the first 32KB.
2026-01-21 09:52:23 +00:00
Rosa Gutierrez 967f76de99 Fix notification broadcast test for turbo-rails 2.0.21
turbo-rails 2.0.21 changed `capture_turbo_stream_broadcasts` to only
return broadcasts produced within the block, matching its documented
behavior (see hotwired/turbo-rails#736).

Since `broadcast_unread` uses `broadcast_prepend_later_to` (async via
job), we need to wrap the call in `perform_enqueued_jobs` so the
broadcast actually happens within the assertion block.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-19 19:49:35 +01:00
Rosa Gutierrez ee636f1994 Delete orphaned watches and pins when a card is moved to a private board
For watchers and users with pins that don't have access to the private
board where the card was moved to.
2026-01-14 19:38:15 +01:00
Rosa Gutierrez 5e099c4c06 Delete pins belonging to cards in a board with revoked access
As part of cleaning up inaccessible data in the board after revoking
access.
2026-01-13 16:25:59 +01:00
Jeremy Daer 006e61ea58 Allow ActionText embed reuse and safe purge (#2346) 2026-01-12 10:30:18 -08: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
Jorge Manrubia 3457c871e1 Revert "Add CJK (Chinese, Japanese, Korean) search support" 2026-01-08 21:07:14 +01:00
Jorge Manrubia db804b8450 Merge pull request #2299 from Maklu/fix-cjk-search
Add CJK (Chinese, Japanese, Korean) search support
2026-01-08 12:00:46 +01:00
Jorge Manrubia e34716a537 Merge pull request #2221 from pinzonjulian/patch-1
Fix test state pollution caused by filter/search_test
2026-01-07 16:40:18 +01:00
Jorge Manrubia 8efb778f53 Merge pull request #2237 from dilberryhoundog/fix-closed-cards-from-not_now
Fix: destroy "not now" state when closing cards, enables "indexed_by=closed" expected behaviour
2026-01-07 16:15:14 +01:00
LU 366588e263 Add SQLite FTS5 support for CJK search
- Split each CJK character into individual tokens for FTS5 indexing
- Add stem_content callback to SQLite adapter to pre-process content
- Stem search queries before matching against FTS5 index
- Update stemmer tests to reflect character-splitting behavior

This enables CJK search on SQLite installations where the FTS5
tokenizer cannot natively segment CJK text.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-06 17:51:20 +09:00
LU 2a4fbecbda Add CJK (Chinese, Japanese, Korean) search support
The search functionality was silently dropping CJK characters because:

1. Query sanitization used `\w` which only matches ASCII word characters
2. Stemmer split by whitespace, which doesn't work for CJK languages
3. Highlighter used `\b` word boundaries that don't apply to CJK

This commit fixes all three issues:

- Query: Use `\p{L}\p{N}` (Unicode letters/numbers) instead of `\w`
- Stemmer: Preserve CJK characters as-is without stemming, since CJK
  languages don't have stemming rules like English
- Highlighter: Skip word boundary matching for CJK terms

Also extracts `CJK_PATTERN` to `Search` module to avoid duplication.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2026-01-06 17:22:42 +09:00
Jorge Manrubia 85bfa7aa48 Rename method 2026-01-05 16:01:25 +01:00
Jorge Manrubia 5f38e8957e Reapply "Make sure new card drafts are refreshed when reused"
This reverts commit 045ddf78f3.
2026-01-05 15:58:29 +01:00
Jorge Manrubia 045ddf78f3 Revert "Make sure new card drafts are refreshed when reused"
This reverts commit 3008011175.
2026-01-05 15:58:16 +01:00
Jorge Manrubia 3008011175 Make sure new card drafts are refreshed when reused
To deal with old timestamps messing with card ordering and so.

https://app.fizzy.do/5986089/cards/3495
2026-01-05 15:57:48 +01:00
Jeremy Daer 0eefd67b68 Block IPv4-compatible IPv6 addresses in SSRF protection (#2273)
The SSRF filter checked ipv4_mapped? but not ipv4_compat?, allowing
addresses like ::169.254.169.254 to bypass the link-local check and
reach cloud metadata endpoints.

Changes:
- Add ipv4_compat? check to block deprecated IPv4-compatible format
- Rename private_address? to blocked_address? (more accurate - method
  blocks more than just RFC 1918 private ranges)
- Add IPv6 test coverage for both mapped and compat formats

Both ipv4_mapped and ipv4_compat formats are blocked entirely as
defense-in-depth: DNS never returns these formats, so they only
appear in attack scenarios.

HackerOne: #3481701
2025-12-29 21:45:06 -08:00
Dylan a005391724 Fix: allow destroying "not now" state when closing cards
- Update `Card::Closeable` to destroy associated "not now" state during close.
- Add new tests to verify closure behavior for different column types, including "not now".
2025-12-23 13:43:03 +08:00
Stanko K.R. 0df667f4fb Fix HTML injection in webhooks through card titles 2025-12-22 12:20:32 +01:00
Julián Pinzón Eslava 12928f530b Refactor search test to use instance variables
The included SearchTestHelper disables transactional tests and has a specific setup that does not use fixtures.

This test however, ignores the setup and uses fixtures directly by creating comments and publishing cards making test state unpredictable. 

By using the records set by the helper instead of fixtures, the state of the system is not polluted.
2025-12-21 00:23:21 +11: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
Rosa Gutierrez cb01966439 Remove unused Card.published_or_drafted_by scope
As the tests for it could lead to confusion where it seems drafted cards
are not accessible to someone with access to the board and with the
direct drafted card URL.
2025-12-19 09:46:21 +01:00
Stanko Krtalić 42185a8d6d Merge pull request #2153 from basecamp/limit-the-number-of-asignees-on-a-card
Limit the number of asignees on a card
2025-12-18 12:08:45 +01:00
Mike Dalessio 2b388dd241 Merge pull request #2164 from basecamp/flavorjones/user-validate-name
Validate User name presence and handle blank names gracefully
2025-12-16 10:49:13 -05:00
Jorge Manrubia ee80b87c8c Add billing system with Stripe
🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Jason Zimdars <jz@37signals.com>
2025-12-16 16:44:20 +01:00
Mike Dalessio b75d2ae6d8 Validate User name presence and handle blank names gracefully
Addresses an issue where User#familiar_name assumed `name` was always
present, potentially raising an exception during view rendering. Now
User validates name presence, and User#familiar_name handles blank
strings without error, in case any existing invalid records exist.
2025-12-16 10:44:10 -05:00
Stanko K.R. 15daa2bf05 Limit asignees to 10 per card 2025-12-15 16:40:52 +01:00