9964 Commits

Author SHA1 Message Date
Jorge Manrubia a761d809ae Add 1GB storage limit with self-host notice for SaaS
Limit free storage to 1GB on the SaaS version. When exceeded, card
publishing, comment creation, and JSON card creation are blocked,
and the card footer shows a "self-host Fizzy for unlimited storage"
notice instead of the create buttons. A nearing-limit warning
appears when usage exceeds 500MB.

Uses the same SaaS engine patterns as the removed billing system:
model concern on Account, controller concerns included via
config.to_prepare, view partials in saas/ with Fizzy.saas? guards
in the main app.
2026-03-17 12:21:32 +01:00
Adrien Maston 768e7a3a6e Merge pull request #2689 from basecamp/mobile/tweak-card-perma-footer
Mobile / Tweak card perma footer
2026-03-17 12:05:47 +01:00
Adrien Maston 2d1e201946 Add scope selector value 2026-03-17 11:57:59 +01:00
Jorge Manrubia 19dbdc6109 Merge pull request #2712 from basecamp/remove-billing-and-limits
Remove payment/subscription system and card/storage limits
2026-03-17 11:15:38 +01:00
Adrien Maston 0b29a67e17 Change date and closedBy properties for a more generic description 2026-03-17 11:09:37 +01:00
Jorge Manrubia 964915bc66 Remove payment/subscription system and card/storage limits
Fizzy is now free. Remove the entire Stripe billing system,
subscription management, and card/storage limit enforcement.

Removes from saas/: Plan model, Account::Billing, Account::Subscription,
Account::Limited, Account::OverriddenLimits, Account::BillingWaiver,
all subscription/billing controllers and views, Stripe webhook handler,
card creation/publishing limit enforcement, admin account override UI,
usage report rake task, and all related tests.

Removes from main app: Fizzy.saas? guards for subscription panel,
SaaS card footer override, near-limit notices, and saas.css stylesheet.

Adds migration to drop billing tables from the SaaS database.

Non-billing SaaS features (push notifications, signup, authorization,
telemetry, console1984/audits1984) are preserved.
2026-03-17 09:22:04 +01:00
Stanko Krtalić bead275d27 Merge pull request #2710 from basecamp/conditionally_disable_peer_verification_for_zip_streaming
Conditionally disable peer verification for ZIP streaming
2026-03-16 18:16:31 +01:00
Stanko Krtalić eb3797b72c Merge pull request #2709 from basecamp/fix-html-injecton-through-import-filenames
Prevent HTML injection through filenames
2026-03-16 17:57:38 +01:00
Stanko K.R. ddc6ce020e Conditionally disable peer verification for ZIP streaming 2026-03-16 17:57:26 +01:00
Stanko K.R. 8f6fca94fa Prevent HTML injection through filenames 2026-03-16 17:50:31 +01:00
github-actions[bot] d579b6409c Sync Gemfile.saas.lock 2026-03-13 20:25:58 +00:00
dependabot[bot] 521b7ffb85 Bump aws-sdk-s3 from 1.213.0 to 1.215.0
Bumps [aws-sdk-s3](https://github.com/aws/aws-sdk-ruby) from 1.213.0 to 1.215.0.
- [Release notes](https://github.com/aws/aws-sdk-ruby/releases)
- [Changelog](https://github.com/aws/aws-sdk-ruby/blob/version-3/gems/aws-sdk-s3/CHANGELOG.md)
- [Commits](https://github.com/aws/aws-sdk-ruby/commits)

---
updated-dependencies:
- dependency-name: aws-sdk-s3
  dependency-version: 1.215.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-03-13 20:25:49 +00:00
github-actions[bot] d9f3301125 Sync Gemfile.saas.lock 2026-03-13 20:25:45 +00:00
dependabot[bot] b3fe9cba82 Bump thruster from 0.1.18 to 0.1.19
Bumps [thruster](https://github.com/basecamp/thruster) from 0.1.18 to 0.1.19.
- [Changelog](https://github.com/basecamp/thruster/blob/main/CHANGELOG.md)
- [Commits](https://github.com/basecamp/thruster/compare/v0.1.18...v0.1.19)

---
updated-dependencies:
- dependency-name: thruster
  dependency-version: 0.1.19
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-03-13 20:25:34 +00:00
github-actions[bot] 09ac251445 Sync Gemfile.saas.lock 2026-03-13 20:24:48 +00:00
dependabot[bot] e5fd8e30de Bump faker from 3.6.0 to 3.6.1 in the development-dependencies group
Bumps the development-dependencies group with 1 update: [faker](https://github.com/faker-ruby/faker).


Updates `faker` from 3.6.0 to 3.6.1
- [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.6.0...v3.6.1)

---
updated-dependencies:
- dependency-name: faker
  dependency-version: 3.6.1
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: development-dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-03-13 20:24:36 +00:00
github-actions[bot] 585375b3ec Sync Gemfile.saas.lock 2026-03-12 17:36:05 +00:00
dependabot[bot] ad30df0ac5 Bump action_text-trix from 2.1.16 to 2.1.17
Bumps [action_text-trix](https://github.com/basecamp/trix) from 2.1.16 to 2.1.17.
- [Release notes](https://github.com/basecamp/trix/releases)
- [Commits](https://github.com/basecamp/trix/compare/v2.1.16...v2.1.17)

---
updated-dependencies:
- dependency-name: action_text-trix
  dependency-version: 2.1.17
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-03-12 17:35:54 +00:00
Mike Dalessio 4211e20a66 Merge pull request #2695 from basecamp/revert-lexxy-080
Revert "Configure Lexxy to add extra spacing between block elements"
2026-03-12 12:28:23 -04:00
Mike Dalessio 3959d91148 Revert "Configure Lexxy to add extra spacing between block elements"
This reverts commit d6bf103efd.
2026-03-12 12:24:35 -04:00
Mike Dalessio af935c9bfe Merge pull request #2692 from basecamp/lexxy-markdown-paste
Configure Lexxy to add extra spacing between block elements
2026-03-12 09:29:39 -04:00
Mike Dalessio d6bf103efd Configure Lexxy to add extra spacing between block elements
Updating Lexxy to v0.8.0 for the insert-markdown event.
2026-03-11 22:17:07 -04:00
Mike Dalessio ab5283441d SaaS usage reporting (#2690)
* Add saas:usage_report rake task and extract Subscription.paid scope

Add a rake task to generate a CSV usage report with per-account data:
Queenbee ID, sign up date, paid date, card count, storage used, and
last active date.

Extract the paid subscriptions query from Admin::StatsController into
an Account::Subscription.paid scope so both the controller and the
new rake task can share it.

* Add comped and account name columns to usage report

* Update saas/lib/tasks/fizzy/usage_report.rake

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Preload storage_total to avoid N+1 in usage report

* Batch last_active_at queries to avoid per-account aggregates

* Add tests for Account::Subscription.paid scope

* Update saas/app/models/account/subscription.rb

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Aggregate paid dates in SQL instead of loading all subscriptions

* Fix paid scope to derive plan keys from Plan.all instead of PLANS hash

* Move paid dates and comped lookups into per-batch queries

* Materialize batch IDs to avoid cross-database subquery

SaasRecord models live on a separate database (fizzy_saas) that doesn't
have the accounts table. Using batch.select(:id) generated a subquery
that ran on the saas database, causing a table-not-found error. Using
pluck(:id) materializes the IDs into an array instead.

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-03-11 10:11:13 -04:00
Adrien Maston 31975be1fa Update app/models/user/named.rb
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-03-11 13:36:09 +01:00
Adrien Maston 7a6c905a32 Prevent line breaks in the middle of familiar_name 2026-03-11 13:20:40 +01:00
Adrien Maston 99e5af6f95 Tweak the layout
... left aligning
2026-03-11 13:19:48 +01:00
Jeffrey Hardy 801da17cfa Merge pull request #2687 from basecamp/fix-card-filing-keyboard-shortcuts
Fix that the "maybe" stream wouldn't be replaced after triaging a card
2026-03-10 16:21:10 -04:00
Mike Dalessio 1e40457418 Merge pull request #2686 from basecamp/flavorjones/fix-path-traversal
Fix path traversal in ActiveStorage blob key import
2026-03-10 15:40:48 -04:00
Mike Dalessio fd20a028a0 Merge pull request #2678 from basecamp/verify-import-models
Validate polymorphic types in import against allowlist
2026-03-10 15:36:06 -04:00
Stanko K.R. b4fd22644c Set the list of importable models based on the record sets in the manifest 2026-03-10 15:33:24 -04:00
Mike Dalessio 8fb57ace01 Validate polymorphic types against importable models allowlist
Add IMPORTABLE_MODEL_NAMES to RecordSet and verify polymorphic type
columns during import check against this allowlist before calling
constantize. This prevents arbitrary class instantiation from
untrusted import ZIP data.
2026-03-10 15:33:24 -04:00
Jeffrey Hardy 0f5216f433 Fix that the "maybe" stream wouldn't be replaced after triaging a card
The element was originally named "the-stream" and later renamed to "maybe",
but the original references weren't updated.

Refs:
- https://github.com/basecamp/fizzy/pull/2211
- https://github.com/basecamp/fizzy/commit/b3cfae35
2026-03-10 14:50:32 -04:00
Rosa Gutierrez 66039c92f3 Use x_user_agent cookie for platform detection in Hotwire Native
Turbo's offline/service worker sets an x_user_agent cookie with the
original browser user agent. This ensures platform detection works
correctly for Hotwire Native apps where service worker requests may
not carry the overridden user agent header.
2026-03-10 15:34:05 +00:00
Rob Zolkos 8c2318e267 Add JSON response format to entropy endpoints (#2673)
* Add JSON response format to entropy endpoints

Add account settings JSON endpoint returning account info including auto_postpone_period. Add auto_postpone_period to board JSON responses. Add JSON update support to both account and board entropy controllers.

* Add auto_postpone_period to all board response examples in API docs

* Use auto_postpone_period_in_days in JSON responses and API docs

* Use valid period values in permission tests
2026-03-10 11:31:00 -04:00
Mike Dalessio 71b99c1e18 Fix path traversal vulnerability in ActiveStorage blob key import
BlobRecordSet#import_batch preserved blob keys from exported ZIP files
verbatim. A crafted ZIP with a traversal key like "../../config/deploy.yml"
would create a blob whose key resolves to an arbitrary filesystem path
when served by ActiveStorage's DiskService, allowing an attacker to read
local files.

Fix: generate fresh blob keys on import, discarding whatever key was in
the ZIP. This is safe because nothing else in the import pipeline
references blobs by key — attachments use blob_id, ActionText uses GIDs
based on record IDs, and only FileRecordSet used the old key for file
data lookup.

Update FileRecordSet to build an old_key→blob_id mapping from the blob
JSON metadata in the ZIP, then look up blobs by ID instead of by key.
Both check_record and import_batch are now fail-closed: they raise
IntegrityError for unmapped storage files, missing blobs, and duplicate
keys in the export.

Backfill test coverage for BlobRecordSet and FileRecordSet import, and
add a round-trip test verifying blob data survives export/import with
regenerated keys.
2026-03-10 11:03:53 -04:00
Alexander Zaytsev c977d03692 Use fire() instead of dispatch()
Addresses Copilot's feedback
2026-03-10 11:45:37 +01:00
Rosa Gutierrez 1cf0406d81 Merge pull request #2582 from basecamp/offline-mode
Offline mode for visited/loaded resources
2026-03-10 10:18:22 +00:00
Adrien Maston 1d523a98f9 Use it on the home's "Add board" button 2026-03-10 10:53:15 +01:00
Adrien Maston 0483ae5302 Add support for "bridge-slot" parameter 2026-03-10 10:52:47 +01:00
Rosa Gutierrez 49a86c7cbf Enable CORS fetch mode for storage requests
The load balancer now returns a specific `Access-Control-Allow-Origin`
instead of a wildcard, so credentials can be included by default.
This enables `maxEntrySize` enforcement for storage.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-10 10:20:54 +01:00
Rosa Gutierrez 1855490f80 Revert CORS fetch mode for Active Storage
CORS mode doesn't work with redirect-based Active Storage URLs
when the storage bucket uses wildcard Access-Control-Allow-Origin.
Relying on maxEntries alone until specific origin CORS is available.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-10 10:20:54 +01:00
Rosa Gutierrez 67d629f722 Use CORS fetch mode for Active Storage to enable maxEntrySize checks
Otherwise, for resources like images loaded via <img> tags, the browser
sets `mode: "no-cors"` (as these aren't CORS requests), so the service
worker gets an opaque response even though the server sends CORS
headers.

We could upgrade all no-cors requests to cors mode, sending a `mode:
"cors"` request to a server that doesn't send CORS headers will fail
entirely: the `fetch` call throws a `TypeError` (network error), and the
browser blocks the response. So the resource wouldn't load at all, not
even as opaque. We wouldn't be able to cache it at all.

By opting in via `fetchOptions`, we can do it only for rules where we
know the server will send CORS headers.
2026-03-10 10:20:54 +01:00
Rosa Gutierrez 99382db720 Only start offline mode when signed in
Gate Turbo.offline.start() behind Current.user so the service worker
is only registered for authenticated users. This avoids errors on
unauthenticated pages where /service-worker.js requires auth.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-10 10:20:54 +01:00
Rosa Gutierrez 0482670fec Exclude service worker and edit/pin/watch/new pages from main cache
We don't need the service worker to cache itself, or pages that won't
work offline anyway.
2026-03-10 10:20:54 +01:00
Rosa Gutierrez de6d70cc2e Bump turbo-rails for Turbo.offline.clearCache()
Simplify the clear-offline-cache controller to use the new
Turbo.offline.clearCache() API instead of messaging the service
worker directly.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-10 10:20:54 +01:00
Rosa Gutierrez 1c2de6ca79 Clear offline cache on sign-in as well as sign-out
Rename logout controller to clear-offline-cache and attach it to the
magic link verification form so the service worker cache is cleared
when a different user signs in.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-10 10:20:54 +01:00
Rosa Gutierrez 089fcf94f8 Preload resources after service worker installation
When the service worker is registered for the first time, resources loaded
before it becomes active won't go through the service worker. These resources
may be served from the browser's HTTP cache on subsequent requests, bypassing
the service worker cache entirely.

The new `preload` option accepts a regex pattern. On first visit (when no
controller exists), it waits for the service worker to take control, then
sends a message with URLs from `performance.getEntriesByType("resource")`
that match the pattern. The service worker fetches and caches these resources.
2026-03-10 10:20:54 +01:00
Rosa Gutierrez b93808235c Limit cached entry sizes
1MB and no limit for number of entries except for attachments, where we
limit individual entries to 2MB and total of entries to 500. The number
is based on the following percentiles for Active Storage blobs:

```
p50: 97.1044921875 KB
p75: 236.9140625 KB
p90: 917.7548828125 KB
```
2026-03-10 10:20:54 +01:00
Rosa Gutierrez 9b264483d1 Bump turbo-rails for cache size limits 2026-03-10 10:20:54 +01:00
Rosa Gutierrez 32134be882 Bump turbo-rails for network-first strategy fix on network error 2026-03-10 10:20:54 +01:00