* Detect dependency requirement drift in bin/bundle-drift
The checker only compared resolved spec versions between Gemfile.lock
and Gemfile.saas.lock. When dependabot bumped solid_queue from ~> 1.3
to ~> 1.4, the forward command patched the resolved version but left
the stale dependency requirement. The checker didn't catch it because
both lockfiles resolved to the same version.
Now find_drift also compares dependency requirements for shared direct
dependencies, and forward patches the DEPENDENCIES section too.
Includes built-in self-test (bin/bundle-drift self-test).
* Sync solid_queue dependency requirement in Gemfile.saas.lock
Updates ~> 1.3 to ~> 1.4 to match Gemfile.lock.
Move MinIO from minio.localhost to minio.fizzy.localhost, which makes
it same-site with the app, so the CORS redirect succeeds.
The service worker fetches Active Storage URLs with `mode: "cors"` so
it can inspect response sizes for offline caching. Active Storage's
redirect controller returns a 302 to the MinIO presigned URL. When
that redirect crosses site boundaries (from fizzy.localhost to
minio.localhost), the browser sets the Origin header to "null" on the
redirected request per the Fetch spec, which fails the CORS check and
produces net::ERR_FAILED.
* Upgrade addressable from 2.8.9 to 2.9.0
Security release fixing ReDoS vulnerabilities in Addressable::Template#match.
No application impact — fizzy does not use Addressable::Template directly.
* dep: fix saas drift
* 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.
* 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.
* 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>
* Document avatar removal API endpoint
* Document notification settings API endpoints
* Document access token list and delete API endpoints
* Document join code API endpoints
* Fix access token delete URL parameter name in docs
* Split docs/API.md into per-resource sections under docs/api/
* Move API endpoints index above authentication in docs/api/README.md
* Fix trailing comma in boards.md JSON example
* Fix trailing comma in cards.md, add Response label to identity.md, add Rich Text to endpoint index
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
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.
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
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.
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.
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.