- Fix indentation
- Split awkward initializer into separate methods
- Rename credentials to passkeys
- Simplify authenticator registry loading
- Flatten nested errors under ActionPack::WebAuthn
- Set passkey current params only requests that use it
- Inline anemic methods
- Replace custom validation with ActiveModel::Validation
- Rename identity to holder in Passkey
- Rename credentials to passkeys in JS
- Extract framework library out of controllers
- Pass params hashes down to ActionPack
- Attempt to simplify public interface
- Push data decoding down to the classes representing the data
- Introduce has_passkeys
- Add CBOR bigint support
- Rename ActionPack::WebAuthn::Passkey to ActionPack::Passkey
- Add create_passkey_button helper
- Rename public-key to creation-options
- Add sign_in_with_passkey_button helper
- Dispatch events for the whole Passkey lifecycle
- Add ED25519 support
- Prevent crash on missing meta tag
- Validate resident key options
- Don't clobber existing ActionPack config options
- Validate cryptographic params
- Move CurrentWebAuthnRequest into ActionPack::Passkey
- Use ActiveModel::Attributes for Options objects
- Implement expiring challanges
- Add lifecycle events
- Extract param helpers into Request
- Add passkey_creation_options and passkey_request_options helpers
- Add create_passkey_challenge to make it easier to override the create method if needed
- Prefix all view helpers with passkey_
- Auto-include ActionPack::Passkey::Holder
- Make the passkey challange url configurable
- Add a reminder about Passkeys to the magic link email
- Use plain client data json everywhere
- Expose AAGUID and backed_up on Credentials
- Make attestation verifiers configurable
- Auto-suggest names for passkeys and show icons
The hidden "Assign to me" button on the card detail view baked
Current.user.id into the form action at render time. When served from
fragment cache, a stale user ID could be embedded, assigning cards to
the wrong person.
Switch to card_self_assignment_path which resolves the current user at
request time via SelfAssignmentsController, matching the fix already
applied to the board view in 2527f073.
ref: https://app.fizzy.do/5986089/cards/3722
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.
* 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
* Add explicit wrap_parameters to controllers for flat JSON API support
Virtual attributes (has_rich_text, has_one_attached, delegated setters,
ActiveModel attrs) are not in Model.attribute_names, so wrap_parameters
auto-detection silently drops them from flat JSON requests. Add explicit
include: lists matching each controller's permitted params.
* Convert short-form wrap_parameters to explicit include: lists
Defense-in-depth: these controllers only have real-column params today,
so auto-detection works, but explicit lists prevent future regressions
if virtual attributes are added.
* Add flat JSON param tests for all wrap_parameters controllers
17 new tests covering every controller with wrap_parameters, verifying
that flat (unwrapped) JSON payloads are correctly wrapped and processed.
Focuses on virtual attributes that would be silently dropped without
explicit include: lists.
* Wrap comment params for flat JSON payloads
The SDK sends flat JSON ({"body": "..."}) but Rails wrap_parameters
auto-detection misses :body since it's a virtual ActionText attribute,
not in Comment.attribute_names. Explicitly declare wrap_parameters so
params.expect(comment: [...]) works with both wrapped and flat payloads.
* Test comment create and update with flat JSON params
* Validate and normalize auto-postpone period to days
- Add Entropy::AUTO_POSTPONE_PERIODS and validate auto_postpone_period against the allowed set
- Introduce auto_postpone_period_in_days for forms and entropy update endpoints
- Fall back to index 0 in knob partial when current value isn't in options
- Remove entropy_auto_close_options helper in favor of model constant
- Update tests to use allowed period values
* Use auto_postpone_period_in_days for JSON entropy updates
The JSON API was accepting auto_postpone_period (seconds) which
bypassed the days normalization and validation. Switch to
auto_postpone_period_in_days consistently and add explicit
wrap_parameters so flat JSON params are wrapped correctly for
the virtual attribute.
* Default to account or 30-day fallback when knob value is invalid
* Address PR review feedback for entropy validation
- Fall back to first knob option (index 0) when persisted value isn't in
the allowed set, preventing nil index errors for legacy values
- Use integer division (1.day.to_i) for consistent day calculations
* Default to account entropy period instead of first option for knob fallback
* Test that default auto-postpone period is in the allowed periods
* Return 422 instead of 500 for invalid entropy auto-postpone values
Rescue ActiveRecord::RecordInvalid in entropy controllers so invalid
auto_postpone_period_in_days values return 422 Unprocessable Entity
instead of raising a 500.
* Fix NoMethodError when board entropy falls back to account default
Use container.account.entropy.auto_postpone_period_in_days instead of
container.account.auto_postpone_period_in_days since Account doesn't
delegate that method.
* Test board entropy fallback to account default for invalid periods
* Add JSON response format to webhooks
Support JSON responses for all webhook endpoints: index, show, create,
update, destroy, and activation. Adds jbuilder views, updates controllers
to use respond_to, and documents the API in docs/API.md.
* Address PR review feedback
- Use distinct board ID in API docs webhook example
- Use render "webhooks/show" in activations controller for consistency
* Address PR review feedback from flavorjones
- Include webhook.board in cache key to avoid stale board data
- Use _url instead of _path for location header consistency
* Fix webhook create test to match Location header format
Use board_webhook_url instead of board_webhook_path since the controller
returns a full URL in the Location header.
* Fix board ID in Location header example in API docs
Use distinct board ID in the Location URL to match the JSON body example.
* Add JSON response format to void-response controller actions
Add respond_to blocks with JSON format to 14 controllers that
previously only rendered HTML or Turbo Stream responses. JSON
callers get head :no_content (or :created/:ok for push
subscriptions, distinguishing new vs existing).
Covers: board involvements, board/account entropies, column
reordering, card readings, card publishing, user roles, user
avatars, account settings, notification settings, join codes
(with 422 error branch), and push subscriptions.
* Add steps index endpoint with JSON view
Add index action to Cards::StepsController so SDK/CLI callers
can list all steps for a card. Reuses the existing _step.json
partial.
* Add JSON views for paginated card list endpoints
Add show.json.jbuilder for stream, not-now, and closed column
endpoints. No controller changes needed — set_page_and_extract_
portion_from and fresh_when already work format-agnostically.
* Add JSON search endpoint with distinct-card pagination
JSON search paginates distinct Card records (via the existing
Card.mentioning scope with .distinct) rather than Search::Record
objects. This ensures page boundaries, Link headers, and counts
reflect unique cards — no short pages from post-pagination dedup.
ID-match searches return a uniform single-element Card[] array
through the same pagination path.
* Add JSON views for settings and configuration endpoints
Add show.json.jbuilder for account settings (name), join codes
(code, usage_count, usage_limit, url, active), and notification
settings (bundle_email_frequency). Tests for show and update
were added in the void-response commit.
* Add JSON response format to data exports
Create returns 201 with export id, status, and created_at so
callers can poll. Show returns any-status exports for JSON (not
just completed) with a download_url when ready, or 404 for
missing/other-user exports.
* Extend access token JSON API
Add index.json and _access_token.json partial for listing tokens.
Add JSON format to destroy. Include id and created_at in the
create response alongside the existing token/description/permission
fields.
* Fix ambiguous precedence warning in export JSON view
* Assert X-Total-Count pagination header in streams JSON test
* Address review feedback
- Guard steps index against HTML requests (redirect to card)
- Normalize created_at to UTC in access token and export JSON
- Add deterministic ordering (.latest) to search JSON pagination
* Return 406 for HTML requests to steps index instead of redirect
* Add wrap_parameters for flat SDK JSON payload compatibility
Five controllers infer the wrong wrapper key from their class
name, so flat JSON bodies like {"role":"admin"} fail with 400.
Add explicit wrap_parameters declarations:
- Users::RolesController → :user
- Notifications::SettingsController → :user_settings
- Account::JoinCodesController → :account_join_code
- Account::SettingsController → :account
- Boards::EntropiesController → :board
Add integration tests that send flat (unwrapped) JSON payloads
for all seven param-bearing endpoints to prove SDK compatibility.
* Address PR review feedback
- Use 201 Created (not 204) for all create actions: publishes, readings,
left/right positions, push subscriptions
- Simplify push subscription to always return :created (no 200/201 variance)
- Remove superfluous respond_to block from steps#index
- Merge exports#show into single respond_to block
- Move export download_url conditional out of inline args
- Ensure join code active is explicitly boolean
- Remove extra parens from search controller assignment
- Add blank lines between format blocks for readability
* Return JSON bodies on create actions
Create actions for boards, cards, columns, comments, and steps now
render the resource as JSON (via their show views) instead of returning
empty 201 responses with only a Location header. SDKs expect a JSON
body they can deserialize into the created resource.
* Return JSON bodies on reaction create actions
Card and comment reaction creates now render the reaction as JSON
instead of returning empty 201 responses, consistent with the other
resource-creating endpoints.
* Allow access tokens API without account scope
Access tokens are identity-level (not account-scoped), so the
controller needs to work with bearer token auth and no account slug
in the URL. Skip require_account so the bearer token auth path
can proceed.
Also fix involvement test to send params in JSON body (not query
string) to match real SDK usage.
The public closed column was showing draft cards because it queried
`@board.cards.closed` without a `.published` filter. This adds the
missing `.published` scope to match the pattern used elsewhere in the
public controllers.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* Add JSON response format to access token creation
Allows programmatic clients (e.g., CLI) to create access tokens
via the API by returning the token, description, and permission
as JSON instead of redirecting to the HTML show page.
* Add JSON response format to access token creation
Allows programmatic clients (e.g., CLI) to create access tokens
via the API by returning the token, description, and permission
as JSON instead of redirecting to the HTML show page.
* Style
---------
Co-authored-by: David Heinemeier Hansson <david@hey.com>
* Fix N+1 queries on identity in UsersController and BoardsController
- Add .includes(:identity) to UsersController#index
- Add .includes(creator: :identity) to BoardsController#index
- Add N+1 regression tests for both controllers
* Simplify controller N+1 tests with query assertions
---------
Co-authored-by: Mike Dalessio <mike@37signals.com>
Include identity.id in the my/identity.json response and return an
empty body from the CREATE signup/completions.json endpoint.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Rails now handles the insecure context case natively in
verified_via_header_only? — when Sec-Fetch-Site is missing and the
request is plain HTTP without force_ssl, the request is allowed.
Remove the now-redundant allowed_insecure_context_request? method and
update the test to set ActionDispatch::Http::URL.secure_protocol
alongside Rails.configuration.force_ssl so the upstream check works
correctly in the test environment.
* 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>
We had client-side notification stacking in the tray since launch, but now we want to stack notifications in the notifications page, in API responses and in email bundles.
The board-scoped mention prompt was returning all users with board access,
including those marked as inactive. This adds the `.active` scope to match
the behavior of the account-wide mention prompt.
Fixes https://app.fizzy.do/5986089/cards/3861
When updating a draft card (e.g., adding a background image), the turbo
stream response was using the published card container partial, which
includes the reactions/boost button. Draft cards should use the draft
container partial which intentionally omits reactions.
Fixes https://app.fizzy.do/5986089/cards/3970
The magic link verification endpoint now returns `requires_signup_completion`
to indicate whether the client needs to collect a name and complete signup.
The signup completion endpoint now supports JSON responses with the account ID.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This is so that native API clients can get the session record deleted
server-side. They still need to take care of clearing any cookies
they've set in web views.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* main: (127 commits)
Align board name start so it doesn't get too big
Clean up card/events header layout
Sync email to Stripe when user changes email address (#2432)
Fix IDOR in webhook activation endpoint (#2431)
Add card reactions to API docs and reactions_url to card JSON (#2427)
Remove unnecessary claude plan
Allow boosts on cards (#2411)
Revert "Fix notification click URL by using correct data property"
Add migration to remove draft cards from search index
Guard search indexing with searchable? check
Forbid comments on draft cards
prefactor: update search to use published cards
Fix notification click URL by using correct data property
Wait for service worker to be active before subscribing
Fix stuck state when permission granted but no subscription
Extract Card::Commentable
Include arm64 build in Docker workflow
Remove unnecessary `await` in push handler
Correctly initialise WebPush connection (#2417)
Update models, views, and fixtures for polymorphic reactions
...
The webhook activation controller was using account-scoped lookup
instead of board-scoped lookup, allowing users to reactivate webhooks
on boards they don't have access to.
This was an oversight when board-scoping was added to the main webhooks
controller - the activations controller was missed in that update.
The fix adds the BoardScoped concern to properly restrict webhook
activation to boards the user has explicit access to.
* 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>
- 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>