diff --git a/.github/workflows/publish-image.yml b/.github/workflows/publish-image.yml
index 953f09b72..9d80d143d 100644
--- a/.github/workflows/publish-image.yml
+++ b/.github/workflows/publish-image.yml
@@ -101,7 +101,7 @@ jobs:
- name: Attest image provenance (per-arch)
if: github.event_name != 'pull_request'
- uses: actions/attest-build-provenance@v3.2.0
+ uses: actions/attest-build-provenance@v4.1.0
with:
subject-name: ${{ steps.vars.outputs.canonical }}
subject-digest: ${{ steps.build.outputs.digest }}
diff --git a/Gemfile b/Gemfile
index 17e7496c0..da66e1473 100644
--- a/Gemfile
+++ b/Gemfile
@@ -8,7 +8,7 @@ gem "rails", github: "rails/rails", branch: "main"
gem "importmap-rails"
gem "propshaft"
gem "stimulus-rails"
-gem "turbo-rails"
+gem "turbo-rails", github: "hotwired/turbo-rails", branch: "offline-cache"
# Deployment and drivers
gem "bootsnap", require: false
diff --git a/Gemfile.lock b/Gemfile.lock
index 4e3653aeb..8db6817dc 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -11,6 +11,15 @@ GIT
specs:
useragent (0.16.11)
+GIT
+ remote: https://github.com/hotwired/turbo-rails.git
+ revision: 93ce5bc461cd71af1797e79fcd7e09a19242d080
+ branch: offline-cache
+ specs:
+ turbo-rails (2.0.23)
+ actionpack (>= 7.1.0)
+ railties (>= 7.1.0)
+
GIT
remote: https://github.com/rails/rails.git
revision: 12e24eaf2f0a9613e015653f013dd131317d9bf5
@@ -158,7 +167,7 @@ GEM
bindex (0.8.1)
bootsnap (1.23.0)
msgpack (~> 1.2)
- brakeman (8.0.2)
+ brakeman (8.0.4)
racc
builder (3.3.0)
bundler-audit (0.9.3)
@@ -461,9 +470,6 @@ GEM
trilogy (2.10.0)
bigdecimal
tsort (0.2.0)
- turbo-rails (2.0.23)
- actionpack (>= 7.1.0)
- railties (>= 7.1.0)
tzinfo (2.0.6)
concurrent-ruby (~> 1.0)
unicode-display_width (3.2.0)
@@ -539,7 +545,7 @@ DEPENDENCIES
stimulus-rails
thruster
trilogy (~> 2.10)
- turbo-rails
+ turbo-rails!
useragent!
vcr
web-console!
diff --git a/Gemfile.saas b/Gemfile.saas
index 39aa49a71..384f5b4b7 100644
--- a/Gemfile.saas
+++ b/Gemfile.saas
@@ -5,7 +5,6 @@ git_source(:bc) { |repo| "https://github.com/basecamp/#{repo}" }
gem "activeresource", require: "active_resource"
gem "actionpack-xml_parser" # needed by queenbee for XML request body parsing
-gem "stripe", "~> 18.0"
gem "queenbee", bc: "queenbee-plugin"
gem "fizzy-saas", path: "saas"
gem "console1984", bc: "console1984"
diff --git a/Gemfile.saas.lock b/Gemfile.saas.lock
index 0e35effac..6f9b0d088 100644
--- a/Gemfile.saas.lock
+++ b/Gemfile.saas.lock
@@ -60,6 +60,15 @@ GIT
rails (>= 6.1)
yabeda (~> 0.6)
+GIT
+ remote: https://github.com/hotwired/turbo-rails.git
+ revision: 93ce5bc461cd71af1797e79fcd7e09a19242d080
+ branch: offline-cache
+ specs:
+ turbo-rails (2.0.23)
+ actionpack (>= 7.1.0)
+ railties (>= 7.1.0)
+
GIT
remote: https://github.com/rails/rails.git
revision: 12e24eaf2f0a9613e015653f013dd131317d9bf5
@@ -248,7 +257,7 @@ GEM
bindex (0.8.1)
bootsnap (1.23.0)
msgpack (~> 1.2)
- brakeman (8.0.2)
+ brakeman (8.0.4)
racc
builder (3.3.0)
bundler-audit (0.9.3)
@@ -622,7 +631,6 @@ GEM
stimulus-rails (1.3.4)
railties (>= 6.0.0)
stringio (3.2.0)
- stripe (18.0.1)
thor (1.5.0)
thruster (0.1.18)
thruster (0.1.18-aarch64-linux)
@@ -633,9 +641,6 @@ GEM
trilogy (2.10.0)
bigdecimal
tsort (0.2.0)
- turbo-rails (2.0.23)
- actionpack (>= 7.1.0)
- railties (>= 7.1.0)
tzinfo (2.0.6)
concurrent-ruby (~> 1.0)
unicode-display_width (3.2.0)
@@ -749,10 +754,9 @@ DEPENDENCIES
sqlite3 (>= 2.0)
stackprof
stimulus-rails
- stripe (~> 18.0)
thruster
trilogy (~> 2.10)
- turbo-rails
+ turbo-rails!
useragent!
vcr
web-console!
diff --git a/app/assets/stylesheets/card-perma.css b/app/assets/stylesheets/card-perma.css
index 755dd8f9a..6d966d0ae 100644
--- a/app/assets/stylesheets/card-perma.css
+++ b/app/assets/stylesheets/card-perma.css
@@ -146,37 +146,74 @@
@media (max-width: 639px) {
--meta-spacer-block: 0.75ch;
- inline-size: 100%;
- grid-template-areas:
- "avatars-author text-added"
- "avatars-author text-author"
- "text-updated text-updated"
- "text-assignees text-assignees"
- "avatars-assignees avatars-assignees";
- grid-template-columns: auto 1fr;
+ min-inline-size: 0;
+ gap: calc(var(--meta-spacer-block) / 2);
+ display: flex;
+ flex-wrap: wrap;
.card__meta-text {
border: 0;
padding: 0;
}
- .card__meta-text + .card__meta-text {
+ .card__meta-avatars--author {
+ --btn-size: 1.5em;
+
+ display: initial;
+ margin-inline-end: unset;
+ order: 3;
+ }
+
+ .card__meta-text--added {
+ inline-size: 100%;
+ order: 1;
+ }
+
+ .card__meta-text--author {
+ order: 2;
+ }
+
+ .card__meta-text--updated {
border-block-start: var(--card-border);
- margin-block-start: var(--meta-spacer-block);
+ inline-size: 100%;
+ margin-block-start: calc(var(--meta-spacer-block) * 0.5);
+ order: 4;
padding-block-start: var(--meta-spacer-block);
}
+ .card__meta-text--assignees {
+ margin-block-start: calc(var(--meta-spacer-block) * 3);
+ order: 6;
+ white-space: unset !important;
+ }
+
.card__meta-avatars--assignees {
- margin-inline: 0;
- margin-block-start: var(--meta-spacer-block);
+ margin-inline: 0 var(--meta-spacer-inline);
+ margin-block-start: calc(var(--meta-spacer-block) * 3);
+ order: 5;
+
+ .avatar {
+ display: grid;
+ }
}
- .card__meta-avatars--author {
- display: initial;
- }
+ &:has(.card__meta-avatars--assignees .avatar) {
+ .card__meta-text--assignees {
+ order: 5;
+ }
- .card__meta-avatars--assignees .avatar {
- display: grid;
+ .card__meta-avatars--assignees {
+ margin-block-start: var(--meta-spacer-block);
+ order: 6;
+ }
+ }
+ }
+ }
+
+ &:has(.card__closed) .card__meta {
+ @media (max-width: 639px) {
+ .card__meta-avatars--assignees {
+ display: none;
}
}
}
@@ -241,10 +278,16 @@
@media (max-width: 639px) {
display: grid;
font-size: var(--text-x-small);
+ gap: 1ch 0;
grid-template-columns: 1fr auto;
grid-template-areas:
"meta bg-zoom"
"meta reactions";
+
+ &:not(:has(.reaction)),
+ &:has(.card__background) {
+ column-gap: 2ch;
+ }
}
}
diff --git a/app/controllers/account/entropies_controller.rb b/app/controllers/account/entropies_controller.rb
index 9876e99d8..44c9debe5 100644
--- a/app/controllers/account/entropies_controller.rb
+++ b/app/controllers/account/entropies_controller.rb
@@ -1,13 +1,22 @@
class Account::EntropiesController < ApplicationController
+ wrap_parameters :entropy, include: [ :auto_postpone_period_in_days ]
+
before_action :ensure_admin
def update
- Current.account.entropy.update!(entropy_params)
- redirect_to account_settings_path, notice: "Account updated"
+ @account = Current.account
+ @account.entropy.update!(entropy_params)
+
+ respond_to do |format|
+ format.html { redirect_to account_settings_path, notice: "Account updated" }
+ format.json { render "account/settings/show", status: :ok }
+ end
+ rescue ActiveRecord::RecordInvalid
+ head :unprocessable_entity
end
private
def entropy_params
- params.expect(entropy: [ :auto_postpone_period ])
+ params.expect(entropy: [ :auto_postpone_period_in_days ])
end
end
diff --git a/app/controllers/account/exports_controller.rb b/app/controllers/account/exports_controller.rb
index dbe0a11c1..32fb5bd42 100644
--- a/app/controllers/account/exports_controller.rb
+++ b/app/controllers/account/exports_controller.rb
@@ -6,11 +6,20 @@ class Account::ExportsController < ApplicationController
CURRENT_EXPORT_LIMIT = 10
def show
+ respond_to do |format|
+ format.html
+ format.json { @export ? render(:show) : head(:not_found) }
+ end
end
def create
- Current.account.exports.create!(user: Current.user).build_later
- redirect_to account_settings_path, notice: "Export started. You'll receive an email when it's ready."
+ @export = Current.account.exports.create!(user: Current.user)
+ @export.build_later
+
+ respond_to do |format|
+ format.html { redirect_to account_settings_path, notice: "Export started. You'll receive an email when it's ready." }
+ format.json { render :show, status: :created }
+ end
end
private
@@ -23,6 +32,7 @@ class Account::ExportsController < ApplicationController
end
def set_export
- @export = Current.account.exports.completed.find_by(id: params[:id], user: Current.user)
+ scope = request.format.json? ? Current.account.exports : Current.account.exports.completed
+ @export = scope.find_by(id: params[:id], user: Current.user)
end
end
diff --git a/app/controllers/account/join_codes_controller.rb b/app/controllers/account/join_codes_controller.rb
index 217ee603c..e7306308b 100644
--- a/app/controllers/account/join_codes_controller.rb
+++ b/app/controllers/account/join_codes_controller.rb
@@ -1,4 +1,6 @@
class Account::JoinCodesController < ApplicationController
+ wrap_parameters :account_join_code, include: %i[ usage_limit ]
+
before_action :set_join_code
before_action :ensure_admin, only: %i[ update destroy ]
@@ -10,15 +12,25 @@ class Account::JoinCodesController < ApplicationController
def update
if @join_code.update(join_code_params)
- redirect_to account_join_code_path
+ respond_to do |format|
+ format.html { redirect_to account_join_code_path }
+ format.json { head :no_content }
+ end
else
- render :edit, status: :unprocessable_entity
+ respond_to do |format|
+ format.html { render :edit, status: :unprocessable_entity }
+ format.json { render json: @join_code.errors, status: :unprocessable_entity }
+ end
end
end
def destroy
@join_code.reset
- redirect_to account_join_code_path
+
+ respond_to do |format|
+ format.html { redirect_to account_join_code_path }
+ format.json { head :no_content }
+ end
end
private
diff --git a/app/controllers/account/settings_controller.rb b/app/controllers/account/settings_controller.rb
index 27f47ab99..e4b949070 100644
--- a/app/controllers/account/settings_controller.rb
+++ b/app/controllers/account/settings_controller.rb
@@ -1,14 +1,23 @@
class Account::SettingsController < ApplicationController
+ wrap_parameters :account, include: %i[ name ]
+
before_action :ensure_admin, only: :update
before_action :set_account
def show
- @users = @account.users.active.alphabetically.includes(:identity)
+ respond_to do |format|
+ format.html { @users = @account.users.active.alphabetically.includes(:identity) }
+ format.json
+ end
end
def update
@account.update!(account_params)
- redirect_to account_settings_path
+
+ respond_to do |format|
+ format.html { redirect_to account_settings_path }
+ format.json { head :no_content }
+ end
end
private
diff --git a/app/controllers/boards/columns_controller.rb b/app/controllers/boards/columns_controller.rb
index 4b71e7f3e..d0bd13c55 100644
--- a/app/controllers/boards/columns_controller.rb
+++ b/app/controllers/boards/columns_controller.rb
@@ -1,4 +1,6 @@
class Boards::ColumnsController < ApplicationController
+ wrap_parameters :column, include: %i[ name color ]
+
include BoardScoped
before_action :set_column, only: %i[ show update destroy ]
@@ -18,7 +20,7 @@ class Boards::ColumnsController < ApplicationController
respond_to do |format|
format.turbo_stream
- format.json { head :created, location: board_column_path(@board, @column, format: :json) }
+ format.json { render :show, status: :created, location: board_column_path(@board, @column, format: :json) }
end
end
diff --git a/app/controllers/boards/entropies_controller.rb b/app/controllers/boards/entropies_controller.rb
index e42631eaf..89e262545 100644
--- a/app/controllers/boards/entropies_controller.rb
+++ b/app/controllers/boards/entropies_controller.rb
@@ -1,14 +1,23 @@
class Boards::EntropiesController < ApplicationController
+ wrap_parameters :board, include: [ :auto_postpone_period_in_days ]
+
include BoardScoped
before_action :ensure_permission_to_admin_board
def update
@board.update!(entropy_params)
+
+ respond_to do |format|
+ format.turbo_stream
+ format.json { render "boards/show", status: :ok }
+ end
+ rescue ActiveRecord::RecordInvalid
+ head :unprocessable_entity
end
private
def entropy_params
- params.expect(board: [ :auto_postpone_period ])
+ params.expect(board: [ :auto_postpone_period_in_days ])
end
end
diff --git a/app/controllers/boards/involvements_controller.rb b/app/controllers/boards/involvements_controller.rb
index a904f2989..271b50262 100644
--- a/app/controllers/boards/involvements_controller.rb
+++ b/app/controllers/boards/involvements_controller.rb
@@ -3,5 +3,11 @@ class Boards::InvolvementsController < ApplicationController
def update
@board.access_for(Current.user).update!(involvement: params[:involvement])
+
+ respond_to do |format|
+ format.html
+ format.turbo_stream
+ format.json { head :no_content }
+ end
end
end
diff --git a/app/controllers/boards_controller.rb b/app/controllers/boards_controller.rb
index 604938deb..3d5485f1f 100644
--- a/app/controllers/boards_controller.rb
+++ b/app/controllers/boards_controller.rb
@@ -1,4 +1,6 @@
class BoardsController < ApplicationController
+ wrap_parameters :board, include: %i[ name all_access auto_postpone_period_in_days public_description ]
+
include FilterScoped
before_action :set_board, except: %i[ index new create ]
@@ -26,7 +28,7 @@ class BoardsController < ApplicationController
respond_to do |format|
format.html { redirect_to board_path(@board) }
- format.json { head :created, location: board_path(@board, format: :json) }
+ format.json { render :show, status: :created, location: board_path(@board, format: :json) }
end
end
@@ -88,7 +90,7 @@ class BoardsController < ApplicationController
end
def board_params
- params.expect(board: [ :name, :all_access, :auto_postpone_period, :public_description ])
+ params.expect(board: [ :name, :all_access, :auto_postpone_period_in_days, :public_description ])
end
def grantees
diff --git a/app/controllers/cards/comments/reactions_controller.rb b/app/controllers/cards/comments/reactions_controller.rb
index 0c822360e..d6b202bb3 100644
--- a/app/controllers/cards/comments/reactions_controller.rb
+++ b/app/controllers/cards/comments/reactions_controller.rb
@@ -1,4 +1,6 @@
class Cards::Comments::ReactionsController < ApplicationController
+ wrap_parameters :reaction, include: %i[ content ]
+
include CardScoped
before_action :set_comment
@@ -22,7 +24,7 @@ class Cards::Comments::ReactionsController < ApplicationController
respond_to do |format|
format.turbo_stream { render "reactions/create" }
- format.json { head :created }
+ format.json { render "reactions/show", status: :created }
end
end
diff --git a/app/controllers/cards/comments_controller.rb b/app/controllers/cards/comments_controller.rb
index f2b318813..7aed33c8c 100644
--- a/app/controllers/cards/comments_controller.rb
+++ b/app/controllers/cards/comments_controller.rb
@@ -1,4 +1,5 @@
class Cards::CommentsController < ApplicationController
+ wrap_parameters :comment, include: %i[ body created_at ]
include CardScoped
before_action :set_comment, only: %i[ show edit update destroy ]
@@ -14,7 +15,7 @@ class Cards::CommentsController < ApplicationController
respond_to do |format|
format.turbo_stream
- format.json { head :created, location: card_comment_path(@card, @comment, format: :json) }
+ format.json { render :show, status: :created, location: card_comment_path(@card, @comment, format: :json) }
end
end
diff --git a/app/controllers/cards/publishes_controller.rb b/app/controllers/cards/publishes_controller.rb
index a0378eec3..a544e6979 100644
--- a/app/controllers/cards/publishes_controller.rb
+++ b/app/controllers/cards/publishes_controller.rb
@@ -4,11 +4,17 @@ class Cards::PublishesController < ApplicationController
def create
@card.publish
- if add_another_param?
- card = @board.cards.create!(status: :drafted)
- redirect_to card_draft_path(card), notice: "Card added"
- else
- redirect_to @card.board
+ respond_to do |format|
+ format.html do
+ if add_another_param?
+ card = @board.cards.create!(status: :drafted)
+ redirect_to card_draft_path(card), notice: "Card added"
+ else
+ redirect_to @card.board
+ end
+ end
+
+ format.json { head :created }
end
end
diff --git a/app/controllers/cards/reactions_controller.rb b/app/controllers/cards/reactions_controller.rb
index 4105a4121..aedb4194f 100644
--- a/app/controllers/cards/reactions_controller.rb
+++ b/app/controllers/cards/reactions_controller.rb
@@ -1,4 +1,6 @@
class Cards::ReactionsController < ApplicationController
+ wrap_parameters :reaction, include: %i[ content ]
+
include CardScoped
before_action :set_reactable
@@ -21,7 +23,7 @@ class Cards::ReactionsController < ApplicationController
respond_to do |format|
format.turbo_stream { render "reactions/create" }
- format.json { head :created }
+ format.json { render "reactions/show", status: :created }
end
end
diff --git a/app/controllers/cards/readings_controller.rb b/app/controllers/cards/readings_controller.rb
index 75fdbac19..4b71272f8 100644
--- a/app/controllers/cards/readings_controller.rb
+++ b/app/controllers/cards/readings_controller.rb
@@ -4,11 +4,21 @@ class Cards::ReadingsController < ApplicationController
def create
@notification = @card.read_by(Current.user)
record_board_access
+
+ respond_to do |format|
+ format.turbo_stream
+ format.json { head :created }
+ end
end
def destroy
@notification = @card.unread_by(Current.user)
record_board_access
+
+ respond_to do |format|
+ format.turbo_stream
+ format.json { head :no_content }
+ end
end
private
diff --git a/app/controllers/cards/steps_controller.rb b/app/controllers/cards/steps_controller.rb
index 0b788fe36..4843da232 100644
--- a/app/controllers/cards/steps_controller.rb
+++ b/app/controllers/cards/steps_controller.rb
@@ -1,14 +1,20 @@
class Cards::StepsController < ApplicationController
+ wrap_parameters :step, include: %i[ content completed ]
+
include CardScoped
before_action :set_step, only: %i[ show edit update destroy ]
+ def index
+ fresh_when etag: @card.steps
+ end
+
def create
@step = @card.steps.create!(step_params)
respond_to do |format|
format.turbo_stream
- format.json { head :created, location: card_step_path(@card, @step, format: :json) }
+ format.json { render :show, status: :created, location: card_step_path(@card, @step, format: :json) }
end
end
diff --git a/app/controllers/cards_controller.rb b/app/controllers/cards_controller.rb
index e007527b8..015ecda1f 100644
--- a/app/controllers/cards_controller.rb
+++ b/app/controllers/cards_controller.rb
@@ -1,4 +1,6 @@
class CardsController < ApplicationController
+ wrap_parameters :card, include: %i[ title description image created_at last_active_at ]
+
include FilterScoped
before_action :set_board, only: %i[ create ]
@@ -18,8 +20,8 @@ class CardsController < ApplicationController
end
format.json do
- card = @board.cards.create! card_params.merge(creator: Current.user, status: "published")
- head :created, location: card_path(card, format: :json)
+ @card = @board.cards.create! card_params.merge(creator: Current.user, status: "published")
+ render :show, status: :created, location: card_path(@card, format: :json)
end
end
end
diff --git a/app/controllers/columns/left_positions_controller.rb b/app/controllers/columns/left_positions_controller.rb
index 7161c093f..78e0c0c80 100644
--- a/app/controllers/columns/left_positions_controller.rb
+++ b/app/controllers/columns/left_positions_controller.rb
@@ -4,5 +4,10 @@ class Columns::LeftPositionsController < ApplicationController
def create
@left_column = @column.left_column
@column.move_left
+
+ respond_to do |format|
+ format.turbo_stream
+ format.json { head :created }
+ end
end
end
diff --git a/app/controllers/columns/right_positions_controller.rb b/app/controllers/columns/right_positions_controller.rb
index d43beb662..5d9cc37f9 100644
--- a/app/controllers/columns/right_positions_controller.rb
+++ b/app/controllers/columns/right_positions_controller.rb
@@ -4,5 +4,10 @@ class Columns::RightPositionsController < ApplicationController
def create
@right_column = @column.right_column
@column.move_right
+
+ respond_to do |format|
+ format.turbo_stream
+ format.json { head :created }
+ end
end
end
diff --git a/app/controllers/concerns/set_platform.rb b/app/controllers/concerns/set_platform.rb
index 54e77d517..66de85f12 100644
--- a/app/controllers/concerns/set_platform.rb
+++ b/app/controllers/concerns/set_platform.rb
@@ -7,6 +7,6 @@ module SetPlatform
private
def platform
- @platform ||= ApplicationPlatform.new(request.user_agent)
+ @platform ||= ApplicationPlatform.new(cookies[:x_user_agent].presence || request.user_agent)
end
end
diff --git a/app/controllers/my/access_tokens_controller.rb b/app/controllers/my/access_tokens_controller.rb
index 82cd93842..dc7e9826a 100644
--- a/app/controllers/my/access_tokens_controller.rb
+++ b/app/controllers/my/access_tokens_controller.rb
@@ -1,4 +1,8 @@
class My::AccessTokensController < ApplicationController
+ wrap_parameters :access_token, include: %i[ description permission ]
+
+ skip_before_action :require_account
+
def index
@access_tokens = my_access_tokens.order(created_at: :desc)
end
@@ -24,14 +28,19 @@ class My::AccessTokensController < ApplicationController
format.json do
render status: :created, json: \
- { token: access_token.token, description: access_token.description, permission: access_token.permission }
+ { id: access_token.id, token: access_token.token, description: access_token.description,
+ permission: access_token.permission, created_at: access_token.created_at.utc }
end
end
end
def destroy
my_access_tokens.find(params[:id]).destroy!
- redirect_to my_access_tokens_path
+
+ respond_to do |format|
+ format.html { redirect_to my_access_tokens_path }
+ format.json { head :no_content }
+ end
end
private
diff --git a/app/controllers/notifications/settings_controller.rb b/app/controllers/notifications/settings_controller.rb
index c4aab7f69..a7ae3d8a6 100644
--- a/app/controllers/notifications/settings_controller.rb
+++ b/app/controllers/notifications/settings_controller.rb
@@ -1,4 +1,6 @@
class Notifications::SettingsController < ApplicationController
+ wrap_parameters :user_settings, include: %i[ bundle_email_frequency ]
+
before_action :set_settings
def show
@@ -7,7 +9,11 @@ class Notifications::SettingsController < ApplicationController
def update
@settings.update!(settings_params)
- redirect_to notifications_settings_path, notice: "Settings updated"
+
+ respond_to do |format|
+ format.html { redirect_to notifications_settings_path, notice: "Settings updated" }
+ format.json { head :no_content }
+ end
end
private
diff --git a/app/controllers/searches_controller.rb b/app/controllers/searches_controller.rb
index b1ec5a0d2..70dfa4d4d 100644
--- a/app/controllers/searches_controller.rb
+++ b/app/controllers/searches_controller.rb
@@ -5,10 +5,22 @@ class SearchesController < ApplicationController
@query = params[:q].blank? ? nil : params[:q]
if card = Current.user.accessible_cards.find_by_id(@query)
- @card = card
+ respond_to do |format|
+ format.html { @card = card }
+ format.json { set_page_and_extract_portion_from Current.user.accessible_cards.where(id: card.id) }
+ end
else
- set_page_and_extract_portion_from Current.user.search(@query)
- @recent_search_queries = Current.user.search_queries.order(updated_at: :desc).limit(10)
+ respond_to do |format|
+ format.html do
+ set_page_and_extract_portion_from Current.user.search(@query)
+ @recent_search_queries = Current.user.search_queries.order(updated_at: :desc).limit(10)
+ end
+
+ format.json do
+ set_page_and_extract_portion_from \
+ Current.user.accessible_cards.mentioning(@query, user: Current.user).distinct.latest.preloaded
+ end
+ end
end
end
end
diff --git a/app/controllers/signups/completions_controller.rb b/app/controllers/signups/completions_controller.rb
index de3d81b63..5c2d9568c 100644
--- a/app/controllers/signups/completions_controller.rb
+++ b/app/controllers/signups/completions_controller.rb
@@ -1,4 +1,6 @@
class Signups::CompletionsController < ApplicationController
+ wrap_parameters :signup, include: %i[ full_name ]
+
layout "public"
disallow_account_scope
diff --git a/app/controllers/signups_controller.rb b/app/controllers/signups_controller.rb
index 56614158f..cf43724f1 100644
--- a/app/controllers/signups_controller.rb
+++ b/app/controllers/signups_controller.rb
@@ -1,4 +1,6 @@
class SignupsController < ApplicationController
+ wrap_parameters :signup, include: %i[ email_address ]
+
disallow_account_scope
allow_unauthenticated_access
rate_limit to: 10, within: 3.minutes, only: :create, with: -> { redirect_to new_signup_path, alert: "Try again later." }
diff --git a/app/controllers/users/avatars_controller.rb b/app/controllers/users/avatars_controller.rb
index 55d8f06dd..544398331 100644
--- a/app/controllers/users/avatars_controller.rb
+++ b/app/controllers/users/avatars_controller.rb
@@ -16,7 +16,11 @@ class Users::AvatarsController < ApplicationController
def destroy
@user.avatar.destroy
- redirect_to @user
+
+ respond_to do |format|
+ format.html { redirect_to @user }
+ format.json { head :no_content }
+ end
end
private
diff --git a/app/controllers/users/joins_controller.rb b/app/controllers/users/joins_controller.rb
index 6baa888ae..402dd34a3 100644
--- a/app/controllers/users/joins_controller.rb
+++ b/app/controllers/users/joins_controller.rb
@@ -1,4 +1,6 @@
class Users::JoinsController < ApplicationController
+ wrap_parameters :user, include: %i[ name avatar ]
+
layout "public"
def new
@@ -6,7 +8,11 @@ class Users::JoinsController < ApplicationController
def create
Current.user.update!(user_params)
- redirect_to landing_path
+
+ respond_to do |format|
+ format.html { redirect_to landing_path }
+ format.json { head :no_content }
+ end
end
private
diff --git a/app/controllers/users/push_subscriptions_controller.rb b/app/controllers/users/push_subscriptions_controller.rb
index b511a6b19..c52b89541 100644
--- a/app/controllers/users/push_subscriptions_controller.rb
+++ b/app/controllers/users/push_subscriptions_controller.rb
@@ -1,16 +1,27 @@
class Users::PushSubscriptionsController < ApplicationController
+ wrap_parameters :push_subscription, include: %i[ endpoint p256dh_key auth_key ]
+
before_action :set_push_subscriptions
def index
end
def create
- @push_subscriptions.create_with(user_agent: request.user_agent).create_or_find_by!(push_subscription_params)
+ subscription = @push_subscriptions.create_with(user_agent: request.user_agent).create_or_find_by!(push_subscription_params)
+
+ respond_to do |format|
+ format.html { head :no_content }
+ format.json { head :created }
+ end
end
def destroy
@push_subscriptions.destroy_by(id: params[:id])
- redirect_to user_push_subscriptions_url
+
+ respond_to do |format|
+ format.html { redirect_to user_push_subscriptions_url }
+ format.json { head :no_content }
+ end
end
private
diff --git a/app/controllers/users/roles_controller.rb b/app/controllers/users/roles_controller.rb
index 711308d13..61c3262ed 100644
--- a/app/controllers/users/roles_controller.rb
+++ b/app/controllers/users/roles_controller.rb
@@ -1,10 +1,16 @@
class Users::RolesController < ApplicationController
+ wrap_parameters :user, include: %i[ role ]
+
before_action :set_user
before_action :ensure_permission_to_administer_user
def update
@user.update!(role_params)
- redirect_to account_settings_path
+
+ respond_to do |format|
+ format.html { redirect_to account_settings_path }
+ format.json { head :no_content }
+ end
end
private
diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb
index 7062b85e5..fdc528d3e 100644
--- a/app/controllers/users_controller.rb
+++ b/app/controllers/users_controller.rb
@@ -1,4 +1,6 @@
class UsersController < ApplicationController
+ wrap_parameters :user, include: %i[ name avatar ]
+
before_action :set_user, except: %i[ index ]
before_action :ensure_permission_to_change_user, only: %i[ update destroy ]
diff --git a/app/controllers/webhooks/activations_controller.rb b/app/controllers/webhooks/activations_controller.rb
index 82166043e..2991be51a 100644
--- a/app/controllers/webhooks/activations_controller.rb
+++ b/app/controllers/webhooks/activations_controller.rb
@@ -4,9 +4,12 @@ class Webhooks::ActivationsController < ApplicationController
before_action :ensure_admin
def create
- webhook = @board.webhooks.find(params[:webhook_id])
- webhook.activate
+ @webhook = @board.webhooks.find(params[:webhook_id])
+ @webhook.activate
- redirect_to webhook
+ respond_to do |format|
+ format.html { redirect_to @webhook }
+ format.json { render "webhooks/show", status: :created }
+ end
end
end
diff --git a/app/controllers/webhooks_controller.rb b/app/controllers/webhooks_controller.rb
index 7d79c4587..ab29badaa 100644
--- a/app/controllers/webhooks_controller.rb
+++ b/app/controllers/webhooks_controller.rb
@@ -1,4 +1,6 @@
class WebhooksController < ApplicationController
+ wrap_parameters :webhook, include: %i[ name url subscribed_actions ]
+
include BoardScoped
before_action :ensure_admin
@@ -16,21 +18,45 @@ class WebhooksController < ApplicationController
end
def create
- webhook = @board.webhooks.create!(webhook_params)
- redirect_to webhook
+ @webhook = @board.webhooks.new(webhook_params)
+
+ if @webhook.save
+ respond_to do |format|
+ format.html { redirect_to @webhook }
+ format.json { render :show, status: :created, location: board_webhook_url(@webhook.board, @webhook, format: :json) }
+ end
+ else
+ respond_to do |format|
+ format.html { render :new, status: :unprocessable_entity }
+ format.json { render json: @webhook.errors, status: :unprocessable_entity }
+ end
+ end
end
def edit
end
def update
- @webhook.update!(webhook_params.except(:url))
- redirect_to @webhook
+ if @webhook.update(webhook_params.except(:url))
+ respond_to do |format|
+ format.html { redirect_to @webhook }
+ format.json { render :show }
+ end
+ else
+ respond_to do |format|
+ format.html { render :edit, status: :unprocessable_entity }
+ format.json { render json: @webhook.errors, status: :unprocessable_entity }
+ end
+ end
end
def destroy
@webhook.destroy!
- redirect_to board_webhooks_path
+
+ respond_to do |format|
+ format.html { redirect_to board_webhooks_path }
+ format.json { head :no_content }
+ end
end
private
diff --git a/app/helpers/entropy_helper.rb b/app/helpers/entropy_helper.rb
index 4329dab88..7257ba481 100644
--- a/app/helpers/entropy_helper.rb
+++ b/app/helpers/entropy_helper.rb
@@ -1,8 +1,4 @@
module EntropyHelper
- def entropy_auto_close_options
- [ 3, 7, 30, 90, 365, 11 ]
- end
-
def entropy_bubble_options_for(card)
{
daysBeforeReminder: card.entropy.days_before_reminder,
diff --git a/app/javascript/controllers/clear_offline_cache_controller.js b/app/javascript/controllers/clear_offline_cache_controller.js
new file mode 100644
index 000000000..7caa2e89b
--- /dev/null
+++ b/app/javascript/controllers/clear_offline_cache_controller.js
@@ -0,0 +1,8 @@
+import { Controller } from "@hotwired/stimulus"
+import { Turbo } from "@hotwired/turbo-rails"
+
+export default class extends Controller {
+ clearCache() {
+ Turbo.offline.clearCache()
+ }
+}
diff --git a/app/javascript/controllers/magic_link_controller.js b/app/javascript/controllers/magic_link_controller.js
index 3e2e4618c..8217af5aa 100644
--- a/app/javascript/controllers/magic_link_controller.js
+++ b/app/javascript/controllers/magic_link_controller.js
@@ -15,7 +15,7 @@ export default class extends Controller {
submit() {
if (this.inputTarget.disabled) return
- this.element.submit()
+ this.element.requestSubmit()
this.inputTarget.disabled = true
}
}
diff --git a/app/javascript/controllers/upload_preview_controller.js b/app/javascript/controllers/upload_preview_controller.js
index d7b0bd478..7e25d50a3 100644
--- a/app/javascript/controllers/upload_preview_controller.js
+++ b/app/javascript/controllers/upload_preview_controller.js
@@ -15,7 +15,7 @@ export default class extends Controller {
}
#showFileName() {
- this.fileNameTarget.innerHTML = this.#file.name
+ this.fileNameTarget.innerText = this.#file.name
this.fileNameTarget.removeAttribute("hidden")
this.placeholderTarget.setAttribute("hidden", true)
}
diff --git a/app/javascript/initializers/index.js b/app/javascript/initializers/index.js
index 90ef36a26..1d16ef205 100644
--- a/app/javascript/initializers/index.js
+++ b/app/javascript/initializers/index.js
@@ -1,2 +1,3 @@
import "initializers/current"
import "initializers/bridge/bridge_element"
+import "initializers/offline"
diff --git a/app/javascript/initializers/offline.js b/app/javascript/initializers/offline.js
new file mode 100644
index 000000000..877ffa0ef
--- /dev/null
+++ b/app/javascript/initializers/offline.js
@@ -0,0 +1,9 @@
+import { Turbo } from "@hotwired/turbo-rails"
+
+if (Current.user) {
+ Turbo.offline.start("/service-worker.js", {
+ scope: "/",
+ native: true,
+ preload: /\/assets\//
+ })
+}
diff --git a/app/models/account/data_transfer/active_storage/blob_record_set.rb b/app/models/account/data_transfer/active_storage/blob_record_set.rb
index 1637768f3..0c4a553c1 100644
--- a/app/models/account/data_transfer/active_storage/blob_record_set.rb
+++ b/app/models/account/data_transfer/active_storage/blob_record_set.rb
@@ -13,6 +13,7 @@ class Account::DataTransfer::ActiveStorage::BlobRecordSet < Account::DataTransfe
data = load(file)
data.slice(*attributes).merge(
"account_id" => account.id,
+ "key" => ::ActiveStorage::Blob.generate_unique_secure_token(length: ::ActiveStorage::Blob::MINIMUM_TOKEN_LENGTH),
"service_name" => ::ActiveStorage::Blob.service.name
)
end
diff --git a/app/models/account/data_transfer/active_storage/file_record_set.rb b/app/models/account/data_transfer/active_storage/file_record_set.rb
index 15d359353..421a2e520 100644
--- a/app/models/account/data_transfer/active_storage/file_record_set.rb
+++ b/app/models/account/data_transfer/active_storage/file_record_set.rb
@@ -22,9 +22,12 @@ class Account::DataTransfer::ActiveStorage::FileRecordSet < Account::DataTransfe
def import_batch(files)
files.each do |file|
- key = File.basename(file)
- blob = ::ActiveStorage::Blob.find_by(key: key, account: account)
- next unless blob
+ old_key = file.delete_prefix("storage/")
+ blob_id = old_key_to_blob_id[old_key]
+ raise IntegrityError, "Storage file #{old_key} has no matching blob metadata in export" unless blob_id
+
+ blob = ::ActiveStorage::Blob.find_by(id: blob_id, account: account)
+ raise IntegrityError, "Blob #{blob_id} not found for storage key #{old_key}" unless blob
zip.read(file) do |stream|
blob.upload(stream)
@@ -32,12 +35,31 @@ class Account::DataTransfer::ActiveStorage::FileRecordSet < Account::DataTransfe
end
end
- def check_record(file_path)
- key = File.basename(file_path)
+ def old_key_to_blob_id
+ @old_key_to_blob_id ||= build_old_key_to_blob_id
+ end
- unless zip.exists?("data/active_storage_blobs/#{key}.json") || ::ActiveStorage::Blob.exists?(key: key, account: account)
- # File exists without corresponding blob record - could be orphaned or blob not yet imported
- # We allow this since blob metadata is imported before files
+ def build_old_key_to_blob_id
+ zip.glob("data/active_storage_blobs/*.json").each_with_object({}) do |file, map|
+ data = load(file)
+ old_key = data["key"]
+ if map.key?(old_key)
+ raise IntegrityError, "Duplicate blob key in export: #{old_key}"
+ end
+ map[old_key] = data["id"]
+ end
+ end
+
+ def with_zip(zip)
+ @old_key_to_blob_id = nil
+ super
+ end
+
+ def check_record(file_path)
+ old_key = file_path.delete_prefix("storage/")
+
+ unless old_key_to_blob_id.key?(old_key)
+ raise IntegrityError, "Storage file #{old_key} has no matching blob metadata in export"
end
end
end
diff --git a/app/models/account/data_transfer/manifest.rb b/app/models/account/data_transfer/manifest.rb
index 2159926d5..9de76f9bf 100644
--- a/app/models/account/data_transfer/manifest.rb
+++ b/app/models/account/data_transfer/manifest.rb
@@ -26,20 +26,42 @@ class Account::DataTransfer::Manifest
[
Account::DataTransfer::AccountRecordSet.new(account),
Account::DataTransfer::UserRecordSet.new(account),
- *build_record_sets(::User::Settings, ::Tag, ::Board, ::Column),
+ *build_record_sets(
+ ::User::Settings,
+ ::Tag,
+ ::Board,
+ ::Column
+ ),
Account::DataTransfer::EntropyRecordSet.new(account),
*build_record_sets(
- ::Board::Publication, ::Webhook, ::Access, ::Card, ::Comment, ::Step,
- ::Assignment, ::Tagging, ::Closure, ::Card::Goldness, ::Card::NotNow,
- ::Card::ActivitySpike, ::Watch, ::Pin, ::Reaction, ::Mention,
- ::Filter, ::Webhook::DelinquencyTracker, ::Event,
- ::Notification, ::Notification::Bundle, ::Webhook::Delivery
+ ::Board::Publication,
+ ::Webhook,
+ ::Access,
+ ::Card,
+ ::Comment,
+ ::Step,
+ ::Assignment,
+ ::Tagging,
+ ::Closure,
+ ::Card::Goldness,
+ ::Card::NotNow,
+ ::Card::ActivitySpike,
+ ::Watch,
+ ::Pin,
+ ::Reaction,
+ ::Mention,
+ ::Filter,
+ ::Webhook::DelinquencyTracker,
+ ::Event,
+ ::Notification,
+ ::Notification::Bundle,
+ ::Webhook::Delivery
),
Account::DataTransfer::ActiveStorage::BlobRecordSet.new(account),
*build_record_sets(::ActiveStorage::Attachment),
Account::DataTransfer::ActionText::RichTextRecordSet.new(account),
Account::DataTransfer::ActiveStorage::FileRecordSet.new(account)
- ]
+ ].then { set_importable_model_names(it) }
end
def build_record_sets(*models)
@@ -47,4 +69,10 @@ class Account::DataTransfer::Manifest
Account::DataTransfer::RecordSet.new(account: account, model: model)
end
end
+
+ def set_importable_model_names(record_sets)
+ model_names = record_sets.filter_map { |record_set| record_set.model&.name }
+ record_sets.each { |record_set| record_set.importable_model_names = model_names }
+ record_sets
+ end
end
diff --git a/app/models/account/data_transfer/record_set.rb b/app/models/account/data_transfer/record_set.rb
index 8eeafbe10..2fba9402d 100644
--- a/app/models/account/data_transfer/record_set.rb
+++ b/app/models/account/data_transfer/record_set.rb
@@ -4,12 +4,14 @@ class Account::DataTransfer::RecordSet
IMPORT_BATCH_SIZE = 100
+ attr_accessor :importable_model_names
attr_reader :account, :model, :attributes
- def initialize(account:, model:, attributes: nil)
+ def initialize(account:, model:, attributes: nil, importable_model_names: nil)
@account = account
@model = model
@attributes = (attributes || model.column_names).map(&:to_s)
+ @importable_model_names = importable_model_names || [ model.name ]
end
def export(to:, start: nil)
@@ -113,7 +115,7 @@ class Account::DataTransfer::RecordSet
def check_association_doesnt_exist(data, association, associated_id)
if association.polymorphic?
type_column = association.foreign_type.to_s
- associated_class = data[type_column].constantize
+ associated_class = verify_model_type(data[type_column])
else
associated_class = association.klass
end
@@ -123,6 +125,14 @@ class Account::DataTransfer::RecordSet
end
end
+ def verify_model_type(type_name)
+ if importable_model_names.include?(type_name)
+ type_name.constantize
+ else
+ raise IntegrityError, "Unrecognized model type: #{type_name}"
+ end
+ end
+
def skip_to(file_list, last_id)
index = file_list.index(last_id)
diff --git a/app/models/board/auto_postponing.rb b/app/models/board/auto_postponing.rb
index 4911c5e33..da53692b3 100644
--- a/app/models/board/auto_postponing.rb
+++ b/app/models/board/auto_postponing.rb
@@ -6,9 +6,7 @@ module Board::AutoPostponing
end
private
- DEFAULT_AUTO_POSTPONE_PERIOD = 30.days
-
def set_default_auto_postpone_period
- self.auto_postpone_period ||= DEFAULT_AUTO_POSTPONE_PERIOD unless attribute_present?(:auto_postpone_period)
+ self.auto_postpone_period ||= Entropy::DEFAULT_AUTO_POSTPONE_PERIOD_IN_DAYS.days unless attribute_present?(:auto_postpone_period)
end
end
diff --git a/app/models/board/entropic.rb b/app/models/board/entropic.rb
index beee4883b..d3e48d6ea 100644
--- a/app/models/board/entropic.rb
+++ b/app/models/board/entropic.rb
@@ -2,7 +2,7 @@ module Board::Entropic
extend ActiveSupport::Concern
included do
- delegate :auto_postpone_period, to: :entropy
+ delegate :auto_postpone_period, :auto_postpone_period_in_days, to: :entropy
has_one :entropy, as: :container, dependent: :destroy
end
@@ -12,6 +12,10 @@ module Board::Entropic
def auto_postpone_period=(new_value)
entropy ||= association(:entropy).reader || self.build_entropy
- entropy.update auto_postpone_period: new_value
+ entropy.update! auto_postpone_period: new_value
+ end
+
+ def auto_postpone_period_in_days=(value)
+ self.auto_postpone_period = value.to_i.days.to_i
end
end
diff --git a/app/models/board/publication.rb b/app/models/board/publication.rb
index bd480eecc..20dcc40da 100644
--- a/app/models/board/publication.rb
+++ b/app/models/board/publication.rb
@@ -1,6 +1,6 @@
class Board::Publication < ApplicationRecord
belongs_to :account, default: -> { board.account }
- belongs_to :board
+ belongs_to :board, touch: true
has_secure_token :key
end
diff --git a/app/models/entropy.rb b/app/models/entropy.rb
index 9a93ba4e8..c896fc494 100644
--- a/app/models/entropy.rb
+++ b/app/models/entropy.rb
@@ -1,6 +1,35 @@
class Entropy < ApplicationRecord
+ DEFAULT_AUTO_POSTPONE_PERIOD_IN_DAYS = 30
+ AUTO_POSTPONE_PERIODS_IN_DAYS = [ 3, 7, 30, 90, 365, 11 ].freeze
+ AUTO_POSTPONE_PERIODS_IN_SECONDS = AUTO_POSTPONE_PERIODS_IN_DAYS.map { |n| n.day.in_seconds }.freeze
+
belongs_to :account, default: -> { container.account }
belongs_to :container, polymorphic: true
+ validates :auto_postpone_period, inclusion: { in: AUTO_POSTPONE_PERIODS_IN_SECONDS }
+
after_commit -> { container.cards.touch_all if container }
+
+ def auto_postpone_period_in_days
+ days = auto_postpone_period / 1.day.to_i
+
+ if days.in?(AUTO_POSTPONE_PERIODS_IN_DAYS)
+ days
+ else
+ default_auto_postpone_period_in_days
+ end
+ end
+
+ def auto_postpone_period_in_days=(new_value)
+ self.auto_postpone_period = new_value.to_i.days.to_i
+ end
+
+ private
+ def default_auto_postpone_period_in_days
+ if container.is_a?(Board)
+ container.account.entropy.auto_postpone_period_in_days
+ else
+ DEFAULT_AUTO_POSTPONE_PERIOD_IN_DAYS
+ end
+ end
end
diff --git a/app/models/user/named.rb b/app/models/user/named.rb
index d052a79ad..804e516ee 100644
--- a/app/models/user/named.rb
+++ b/app/models/user/named.rb
@@ -20,6 +20,6 @@ module User::Named
def familiar_name
names = name.split
return name if names.length <= 1
- "#{names.first} #{names[1..].map { |n| "#{n[0]}." }.join}"
+ "#{names.first}\u00A0#{names[1..].map { |n| "#{n[0]}." }.join}"
end
end
diff --git a/app/models/zip_file.rb b/app/models/zip_file.rb
index dc5b7ee8d..3415f5879 100644
--- a/app/models/zip_file.rb
+++ b/app/models/zip_file.rb
@@ -84,7 +84,8 @@ class ZipFile
def read_from_s3(blob)
url = blob.url(expires_in: 6.hour)
- remote_io = RemoteIO.new(url)
+ ssl_verify_peer = blob.service.client.client.config.ssl_verify_peer
+ remote_io = RemoteIO.new(url, ssl_verify_peer: ssl_verify_peer)
reader = Reader.new(remote_io)
yield reader
end
diff --git a/app/models/zip_file/remote_io.rb b/app/models/zip_file/remote_io.rb
index 1cb00831a..59329d811 100644
--- a/app/models/zip_file/remote_io.rb
+++ b/app/models/zip_file/remote_io.rb
@@ -1,4 +1,9 @@
class ZipFile::RemoteIO < ZipKit::RemoteIO
+ def initialize(url, ssl_verify_peer: true)
+ super(url)
+ @ssl_verify_peer = ssl_verify_peer
+ end
+
protected
def request_range(range)
with_http do |http|
@@ -37,8 +42,7 @@ class ZipFile::RemoteIO < ZipKit::RemoteIO
def with_http
http = Net::HTTP.new(@uri.hostname, @uri.port)
http.use_ssl = @uri.scheme == "https"
- # FIXME: Disable SSL verification for now to avoid issues with our self-signed certificates for PureStorage
- http.verify_mode = OpenSSL::SSL::VERIFY_NONE
+ http.verify_mode = OpenSSL::SSL::VERIFY_NONE unless @ssl_verify_peer
http.start { yield http }
end
end
diff --git a/app/views/account/exports/show.json.jbuilder b/app/views/account/exports/show.json.jbuilder
new file mode 100644
index 000000000..2bd9e3d60
--- /dev/null
+++ b/app/views/account/exports/show.json.jbuilder
@@ -0,0 +1,6 @@
+json.(@export, :id, :status)
+json.created_at @export.created_at.utc
+
+if @export.completed? && @export.file.attached?
+ json.download_url rails_blob_url(@export.file, disposition: "attachment")
+end
diff --git a/app/views/account/join_codes/show.json.jbuilder b/app/views/account/join_codes/show.json.jbuilder
new file mode 100644
index 000000000..559e7b6c2
--- /dev/null
+++ b/app/views/account/join_codes/show.json.jbuilder
@@ -0,0 +1,3 @@
+json.(@join_code, :code, :usage_count, :usage_limit)
+json.url join_url(code: @join_code.code, script_name: Current.account.slug)
+json.active !!@join_code.active?
diff --git a/app/views/account/settings/show.html.erb b/app/views/account/settings/show.html.erb
index 91bb85aff..fdb118e57 100644
--- a/app/views/account/settings/show.html.erb
+++ b/app/views/account/settings/show.html.erb
@@ -21,5 +21,3 @@
<%= render "account/settings/cancellation" %>
-
-<%= render "account/settings/subscription_panel" if Fizzy.saas? %>
diff --git a/app/views/account/settings/show.json.jbuilder b/app/views/account/settings/show.json.jbuilder
new file mode 100644
index 000000000..55b623f9d
--- /dev/null
+++ b/app/views/account/settings/show.json.jbuilder
@@ -0,0 +1,3 @@
+json.(@account, :id, :name, :cards_count)
+json.created_at @account.created_at.utc
+json.auto_postpone_period_in_days @account.entropy.auto_postpone_period_in_days
diff --git a/app/views/boards/_board.json.jbuilder b/app/views/boards/_board.json.jbuilder
index 7c85a1dc5..0eeff1896 100644
--- a/app/views/boards/_board.json.jbuilder
+++ b/app/views/boards/_board.json.jbuilder
@@ -1,6 +1,7 @@
json.cache! board do
json.(board, :id, :name, :all_access)
json.created_at board.created_at.utc
+ json.auto_postpone_period_in_days board.auto_postpone_period_in_days
json.url board_url(board)
json.creator board.creator, partial: "users/user", as: :user
diff --git a/app/views/boards/columns/closeds/show.json.jbuilder b/app/views/boards/columns/closeds/show.json.jbuilder
new file mode 100644
index 000000000..c1dc1dff1
--- /dev/null
+++ b/app/views/boards/columns/closeds/show.json.jbuilder
@@ -0,0 +1 @@
+json.array! @page.records, partial: "cards/card", as: :card
diff --git a/app/views/boards/columns/not_nows/show.json.jbuilder b/app/views/boards/columns/not_nows/show.json.jbuilder
new file mode 100644
index 000000000..c1dc1dff1
--- /dev/null
+++ b/app/views/boards/columns/not_nows/show.json.jbuilder
@@ -0,0 +1 @@
+json.array! @page.records, partial: "cards/card", as: :card
diff --git a/app/views/boards/columns/streams/show.json.jbuilder b/app/views/boards/columns/streams/show.json.jbuilder
new file mode 100644
index 000000000..c1dc1dff1
--- /dev/null
+++ b/app/views/boards/columns/streams/show.json.jbuilder
@@ -0,0 +1 @@
+json.array! @page.records, partial: "cards/card", as: :card
diff --git a/app/views/boards/show/_stream.html.erb b/app/views/boards/show/_stream.html.erb
index b43309100..f66fb8fa6 100644
--- a/app/views/boards/show/_stream.html.erb
+++ b/app/views/boards/show/_stream.html.erb
@@ -12,7 +12,7 @@
<% if page.used? %>
- <%= with_automatic_pagination "maybe", @page do %>
+ <%= with_automatic_pagination "maybe", page do %>
<%= render "cards/display/previews", cards: page.records, draggable: true %>
<% end %>
<% end %>
diff --git a/app/views/cards/closures/create.turbo_stream.erb b/app/views/cards/closures/create.turbo_stream.erb
index c4898e471..5b76efe7b 100644
--- a/app/views/cards/closures/create.turbo_stream.erb
+++ b/app/views/cards/closures/create.turbo_stream.erb
@@ -3,7 +3,7 @@
<% if @source_column %>
<%= turbo_stream.replace(dom_id(@source_column), partial: "boards/show/column", method: :morph, locals: { column: @source_column }) %>
<% elsif @was_in_stream %>
- <%= turbo_stream.replace("the-stream", partial: "boards/show/stream", method: :morph, locals: { board: @card.board, page: @page }) %>
+ <%= turbo_stream.replace("maybe", partial: "boards/show/stream", method: :morph, locals: { board: @card.board, page: @page }) %>
<% end %>
<%= turbo_stream.replace([ @card, :card_container ], partial: "cards/container", method: :morph, locals: { card: @card.reload }) %>
diff --git a/app/views/cards/closures/destroy.turbo_stream.erb b/app/views/cards/closures/destroy.turbo_stream.erb
index 8df408979..31a9e41c3 100644
--- a/app/views/cards/closures/destroy.turbo_stream.erb
+++ b/app/views/cards/closures/destroy.turbo_stream.erb
@@ -3,7 +3,7 @@
<% if @card.column %>
<%= turbo_stream.replace(dom_id(@card.column), partial: "boards/show/column", method: :morph, locals: { column: @card.column }) %>
<% elsif @card.awaiting_triage? %>
- <%= turbo_stream.replace("the-stream", partial: "boards/show/stream", method: :morph, locals: { board: @card.board, page: @page }) %>
+ <%= turbo_stream.replace("maybe", partial: "boards/show/stream", method: :morph, locals: { board: @card.board, page: @page }) %>
<% end %>
<%= turbo_stream.replace([ @card, :card_container ], partial: "cards/container", method: :morph, locals: { card: @card.reload }) %>
diff --git a/app/views/cards/container/footer/_create.html.erb b/app/views/cards/container/footer/_create.html.erb
index b55cae344..648beffd1 100644
--- a/app/views/cards/container/footer/_create.html.erb
+++ b/app/views/cards/container/footer/_create.html.erb
@@ -13,7 +13,5 @@
Create and add another
<% end %>
-
- <%= render "cards/container/footer/saas/near_notice" if Fizzy.saas? %>
diff --git a/app/views/cards/drafts/_container.html.erb b/app/views/cards/drafts/_container.html.erb
index fa7134b55..9ffc04711 100644
--- a/app/views/cards/drafts/_container.html.erb
+++ b/app/views/cards/drafts/_container.html.erb
@@ -26,9 +26,5 @@
<% end %>
- <% if Fizzy.saas? %>
- <%= render "cards/container/footer/saas/create", card: card %>
- <% else %>
- <%= render "cards/container/footer/create", card: card %>
- <% end %>
+ <%= render "cards/container/footer/create", card: card %>
diff --git a/app/views/cards/not_nows/create.turbo_stream.erb b/app/views/cards/not_nows/create.turbo_stream.erb
index 9128580e2..945d86ab2 100644
--- a/app/views/cards/not_nows/create.turbo_stream.erb
+++ b/app/views/cards/not_nows/create.turbo_stream.erb
@@ -3,7 +3,7 @@
<% if @source_column %>
<%= turbo_stream.replace(dom_id(@source_column), partial: "boards/show/column", method: :morph, locals: { column: @source_column }) %>
<% elsif @was_in_stream %>
- <%= turbo_stream.replace("the-stream", partial: "boards/show/stream", method: :morph, locals: { board: @card.board, page: @page }) %>
+ <%= turbo_stream.replace("maybe", partial: "boards/show/stream", method: :morph, locals: { board: @card.board, page: @page }) %>
<% end %>
<%= turbo_stream.replace([ @card, :card_container ], partial: "cards/container", method: :morph, locals: { card: @card.reload }) %>
diff --git a/app/views/cards/steps/index.json.jbuilder b/app/views/cards/steps/index.json.jbuilder
new file mode 100644
index 000000000..087790175
--- /dev/null
+++ b/app/views/cards/steps/index.json.jbuilder
@@ -0,0 +1 @@
+json.array! @card.steps, partial: "cards/steps/step", as: :step
diff --git a/app/views/entropy/_auto_close.html.erb b/app/views/entropy/_auto_close.html.erb
index 6ccb5d205..a2e3fe0ab 100644
--- a/app/views/entropy/_auto_close.html.erb
+++ b/app/views/entropy/_auto_close.html.erb
@@ -6,8 +6,9 @@
<%= form_with model: model, url: url, data: { controller: "form" } do |form| %>
<%= render "entropy/knob",
form: form,
- name: :auto_postpone_period,
- knob_options: entropy_auto_close_options,
+ name: :auto_postpone_period_in_days,
+ current_value: model.auto_postpone_period_in_days,
+ knob_options: Entropy::AUTO_POSTPONE_PERIODS_IN_DAYS,
label: "Days until auto-close",
disabled: disabled %>
<% end %>
diff --git a/app/views/entropy/_knob.html.erb b/app/views/entropy/_knob.html.erb
index 69bbb5fb0..ba17c6728 100644
--- a/app/views/entropy/_knob.html.erb
+++ b/app/views/entropy/_knob.html.erb
@@ -1,6 +1,5 @@
<%
- period = form.object.send(name)
- current_index = knob_options.index(period.to_i / 1.day)
+ current_index = knob_options.index(current_value) || knob_options.index(Entropy::DEFAULT_AUTO_POSTPONE_PERIOD_IN_DAYS)
%>