active_storage-async_variants 0.4.0 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +4 -0
- data/app/assets/javascripts/retry.js +11 -0
- data/app/assets/stylesheets/retry.css +98 -0
- data/app/controllers/active_storage/async_variants/states_controller.rb +16 -0
- data/app/views/active_storage/async_variants/states/_failed.html.erb +41 -2
- data/app/views/active_storage/async_variants/states/_processing.html.erb +1 -2
- data/app/views/active_storage/async_variants/states/show.html.erb +2 -0
- data/config/routes.rb +5 -0
- data/features/retry_flow.feature +20 -0
- data/features/retry_visibility.feature +23 -0
- data/features/state_rendering.feature +1 -0
- data/gemfiles/rails_7.2.gemfile +4 -0
- data/gemfiles/rails_8.0.gemfile +4 -0
- data/gemfiles/rails_8.1.gemfile +4 -0
- data/lib/active_storage/async_variants/asset_tag_helper_extension.rb +3 -6
- data/lib/active_storage/async_variants/version.rb +1 -1
- data/lib/active_storage/async_variants.rb +15 -0
- metadata +20 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: bc926d64601c238db11edd005f220c0842cbf4316f405d43c23d08a99ff874de
|
|
4
|
+
data.tar.gz: 177a3d4a3647e41ac5e23db1908265c556a8c4c006b5497cbb802b2cf9ebb02e
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 25f4d2430ce2547c9a4109bd648f338914e92cacdb38bf2e651cdfff21a9a045e90480c10e6170f0723dbcb5e675a35370cda7c91ced43ec41c5bfa3f112f2a1
|
|
7
|
+
data.tar.gz: c02fac50058fd3465435bf194e09ffb0732deda2c61316b8e960b1179e4ee8e9eb417e1dd192fdf564c0e059d87a674fe76aa852f99a385592ddfcd71fb1a453
|
data/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
## [0.5.0]
|
|
2
|
+
|
|
3
|
+
- Added an opt-in retry affordance to the failed state: hovering a failed variant reveals a control that opens a dialog with the error and a "Retry processing" button that re-runs the transform. Disabled by default; enable it with `ActiveStorage::AsyncVariants.retry_visible_if { … }` (the block runs in the view context, so it can check `current_user`).
|
|
4
|
+
|
|
1
5
|
## [0.4.0]
|
|
2
6
|
|
|
3
7
|
- **Breaking:** Replaced the polling-`<img>` architecture with a `<turbo-frame>` for unprocessed variants. `image_tag`/`video_tag` with `async: true` now emits a normal `<img>`/`<video>` when the variant is already processed, and a `<turbo-frame src="…">` otherwise. The frame hits a new gem-shipped endpoint (`GET /active_storage/async_variants/states/:signed_blob_id/:variation_key`) that renders one of `_processing`/`_failed`/`_processed` partials. Non-terminal renders include an inline `<script>` that schedules the frame's next reload, so the state polls itself until it terminates.
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
// Declarative shadow roots are only attached by the initial HTML parser, not by
|
|
2
|
+
// fragment insertion (innerHTML / Turbo frame swaps), so promote them on connect.
|
|
3
|
+
customElements.define("async-variant-retry", class extends HTMLElement {
|
|
4
|
+
connectedCallback() {
|
|
5
|
+
if (this.shadowRoot) return
|
|
6
|
+
const template = this.querySelector(":scope > template[shadowrootmode]")
|
|
7
|
+
if (!template) return
|
|
8
|
+
this.attachShadow({ mode: template.getAttribute("shadowrootmode") }).append(template.content)
|
|
9
|
+
template.remove()
|
|
10
|
+
}
|
|
11
|
+
})
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
/* Scoped to the retry dialog's shadow root (linked inside its <template>). */
|
|
2
|
+
:host { display: contents; }
|
|
3
|
+
|
|
4
|
+
button { font: inherit; cursor: pointer; }
|
|
5
|
+
|
|
6
|
+
.opener {
|
|
7
|
+
position: absolute;
|
|
8
|
+
top: 6px; right: 6px;
|
|
9
|
+
width: 22px; height: 22px;
|
|
10
|
+
background: #2b7cd6;
|
|
11
|
+
color: #fff;
|
|
12
|
+
border: 2px solid #fff;
|
|
13
|
+
border-radius: 50%;
|
|
14
|
+
font-size: 13px;
|
|
15
|
+
font-weight: bold;
|
|
16
|
+
line-height: 1;
|
|
17
|
+
padding: 0;
|
|
18
|
+
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.35);
|
|
19
|
+
display: var(--retry-opener-display, none);
|
|
20
|
+
align-items: center;
|
|
21
|
+
justify-content: center;
|
|
22
|
+
}
|
|
23
|
+
.opener:hover { background: #1a5fb0; }
|
|
24
|
+
|
|
25
|
+
dialog {
|
|
26
|
+
width: 720px;
|
|
27
|
+
max-width: 90vw;
|
|
28
|
+
max-height: 80vh;
|
|
29
|
+
padding: 0;
|
|
30
|
+
border: 1px solid #999;
|
|
31
|
+
border-radius: 6px;
|
|
32
|
+
background: #fff;
|
|
33
|
+
color: #222;
|
|
34
|
+
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3);
|
|
35
|
+
font: 13px/1.4 system-ui, -apple-system, sans-serif;
|
|
36
|
+
}
|
|
37
|
+
dialog[open] { display: flex; flex-direction: column; }
|
|
38
|
+
dialog::backdrop { background: rgba(0, 0, 0, 0.5); }
|
|
39
|
+
|
|
40
|
+
dialog > button {
|
|
41
|
+
position: absolute;
|
|
42
|
+
top: 10px;
|
|
43
|
+
right: 12px;
|
|
44
|
+
width: 32px; height: 32px;
|
|
45
|
+
background: transparent;
|
|
46
|
+
border: none;
|
|
47
|
+
color: #888;
|
|
48
|
+
font-size: 24px;
|
|
49
|
+
line-height: 1;
|
|
50
|
+
padding: 0;
|
|
51
|
+
border-radius: 4px;
|
|
52
|
+
z-index: 1;
|
|
53
|
+
}
|
|
54
|
+
dialog > button:hover { color: #000; background: rgba(0, 0, 0, 0.08); }
|
|
55
|
+
|
|
56
|
+
header { padding: 20px 24px 0 24px; }
|
|
57
|
+
header h3 {
|
|
58
|
+
margin: 0;
|
|
59
|
+
font-size: 16px;
|
|
60
|
+
font-weight: bold;
|
|
61
|
+
color: #111;
|
|
62
|
+
padding-right: 32px;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
section {
|
|
66
|
+
flex: 1 1 auto;
|
|
67
|
+
overflow: auto;
|
|
68
|
+
padding: 12px 24px 20px 24px;
|
|
69
|
+
min-height: 0;
|
|
70
|
+
}
|
|
71
|
+
section pre {
|
|
72
|
+
background: #f5f5f5;
|
|
73
|
+
color: #222;
|
|
74
|
+
border: 1px solid #ddd;
|
|
75
|
+
padding: 8px;
|
|
76
|
+
font: 11px/1.4 ui-monospace, Menlo, monospace;
|
|
77
|
+
white-space: pre-wrap;
|
|
78
|
+
word-break: break-all;
|
|
79
|
+
margin: 0;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
footer {
|
|
83
|
+
flex: 0 0 auto;
|
|
84
|
+
border-top: 1px solid #ddd;
|
|
85
|
+
padding: 12px 20px;
|
|
86
|
+
display: flex;
|
|
87
|
+
justify-content: flex-end;
|
|
88
|
+
background: #fafafa;
|
|
89
|
+
}
|
|
90
|
+
footer button {
|
|
91
|
+
padding: 8px 16px;
|
|
92
|
+
background: #2a7;
|
|
93
|
+
color: #fff;
|
|
94
|
+
border: none;
|
|
95
|
+
border-radius: 4px;
|
|
96
|
+
font-weight: bold;
|
|
97
|
+
}
|
|
98
|
+
footer button:disabled { opacity: 0.5; cursor: wait; }
|
|
@@ -16,6 +16,22 @@ module ActiveStorage
|
|
|
16
16
|
def show
|
|
17
17
|
end
|
|
18
18
|
|
|
19
|
+
def retry
|
|
20
|
+
@variant.blob.variant_records.where(
|
|
21
|
+
variation_digest: @variant.variation.digest,
|
|
22
|
+
state: "failed",
|
|
23
|
+
).destroy_all
|
|
24
|
+
@variant.enqueue!
|
|
25
|
+
|
|
26
|
+
redirect_to Rails.application.routes.url_helpers.async_variant_state_path(
|
|
27
|
+
signed_blob_id: params[:signed_blob_id],
|
|
28
|
+
variation_key: params[:variation_key],
|
|
29
|
+
kind: params[:kind],
|
|
30
|
+
direct: params[:direct],
|
|
31
|
+
opts: async_variant_html_options.presence,
|
|
32
|
+
), status: :see_other
|
|
33
|
+
end
|
|
34
|
+
|
|
19
35
|
helper_method :async_variant_kind, :async_variant_direct?, :async_variant_html_options
|
|
20
36
|
|
|
21
37
|
private
|
|
@@ -1,4 +1,43 @@
|
|
|
1
1
|
<div class="async-variant-state async-variant-failed">
|
|
2
|
-
|
|
3
|
-
|
|
2
|
+
<%= image_tag async_variant_representation_path(variant), **html_options.symbolize_keys.except(:controls, :autoplay, :preload) %>
|
|
3
|
+
|
|
4
|
+
<% if ActiveStorage::AsyncVariants.retry_visible?(self) %>
|
|
5
|
+
<% dialog_id = "#{async_variant_frame_id(variant)}-error" %>
|
|
6
|
+
<% form_id = "#{dialog_id}-form" %>
|
|
7
|
+
<%# Light DOM, so the custom property inherits into the shadow root. %>
|
|
8
|
+
<style>
|
|
9
|
+
.async-variant-failed {
|
|
10
|
+
position: relative;
|
|
11
|
+
display: inline-block;
|
|
12
|
+
--retry-opener-display: none;
|
|
13
|
+
}
|
|
14
|
+
.async-variant-failed:hover { --retry-opener-display: inline-flex; }
|
|
15
|
+
</style>
|
|
16
|
+
<%# In the light DOM so its submit reaches Turbo, and so preventDefault on the
|
|
17
|
+
buttons doesn't also cancel the submit. %>
|
|
18
|
+
<form action="<%= Rails.application.routes.url_helpers.async_variant_state_retry_path(signed_blob_id:, variation_key:, kind:, direct:, opts: html_options) %>"
|
|
19
|
+
method="post"
|
|
20
|
+
id="<%= form_id %>"
|
|
21
|
+
data-turbo-frame="<%= async_variant_frame_id(variant) %>"
|
|
22
|
+
hidden></form>
|
|
23
|
+
<async-variant-retry>
|
|
24
|
+
<template shadowrootmode="open">
|
|
25
|
+
<%= ActiveStorage::AsyncVariants.stylesheet_link_tag "retry" %>
|
|
26
|
+
<button class="opener" type="button"
|
|
27
|
+
onclick="event.preventDefault(); event.stopPropagation(); this.getRootNode().getElementById('<%= dialog_id %>').showModal()"
|
|
28
|
+
title="Variant processing failed — click for details">?</button>
|
|
29
|
+
<dialog id="<%= dialog_id %>">
|
|
30
|
+
<button type="button"
|
|
31
|
+
onclick="event.preventDefault(); event.stopPropagation(); this.closest('dialog').close()">×</button>
|
|
32
|
+
<header><h3>Variant processing failed</h3></header>
|
|
33
|
+
<section><pre><%= variant.error %></pre></section>
|
|
34
|
+
<footer>
|
|
35
|
+
<button type="button"
|
|
36
|
+
onclick="event.preventDefault(); event.stopPropagation(); this.disabled = true; this.textContent = 'Retrying…'; document.getElementById('<%= form_id %>').requestSubmit()">Retry processing</button>
|
|
37
|
+
</footer>
|
|
38
|
+
</dialog>
|
|
39
|
+
</template>
|
|
40
|
+
</async-variant-retry>
|
|
41
|
+
<%= ActiveStorage::AsyncVariants.javascript_include_tag "retry", type: "module" %>
|
|
42
|
+
<% end %>
|
|
4
43
|
</div>
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
<div class="async-variant-state async-variant-processing">
|
|
2
|
-
|
|
3
|
-
<%= send method, async_variant_representation_path(variant), **html_options.symbolize_keys %>
|
|
2
|
+
<%= image_tag async_variant_representation_path(variant), **html_options.symbolize_keys.except(:controls, :autoplay, :preload) %>
|
|
4
3
|
</div>
|
|
5
4
|
<%# Self-perpetuating poll: Turbo runs <script>s in frame swaps, so each
|
|
6
5
|
pending/processing response schedules its own next reload. Terminal
|
data/config/routes.rb
CHANGED
|
@@ -8,4 +8,9 @@ Rails.application.routes.draw do
|
|
|
8
8
|
get "/active_storage/async_variants/states/:signed_blob_id/:variation_key",
|
|
9
9
|
to: "active_storage/async_variants/states#show",
|
|
10
10
|
as: :async_variant_state
|
|
11
|
+
post "/active_storage/async_variants/states/:signed_blob_id/:variation_key/retry",
|
|
12
|
+
to: "active_storage/async_variants/states#retry",
|
|
13
|
+
as: :async_variant_state_retry
|
|
14
|
+
|
|
15
|
+
ActiveStorage::AsyncVariants::Assets.draw(self, "/active_storage/async_variants/assets")
|
|
11
16
|
end
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
Feature: Clicking the retry affordance opens a dialog and resubmits the variant
|
|
2
|
+
|
|
3
|
+
Background:
|
|
4
|
+
Given a user with an attached avatar
|
|
5
|
+
And the retry affordance is visible to everyone
|
|
6
|
+
|
|
7
|
+
Scenario: Clicking the opener reveals the error inside the shadow-DOM dialog
|
|
8
|
+
Given the avatar's :thumb_proc variant is in failed state with error "boom from upstream"
|
|
9
|
+
When I visit the avatar page for the :thumb_proc variant
|
|
10
|
+
And I click the retry opener
|
|
11
|
+
Then the failure dialog should be open
|
|
12
|
+
And the failure dialog should contain "boom from upstream"
|
|
13
|
+
|
|
14
|
+
Scenario: Submitting Retry destroys the failed record and re-enqueues
|
|
15
|
+
Given the avatar's :thumb_proc variant is in failed state with error "boom"
|
|
16
|
+
When I visit the avatar page for the :thumb_proc variant
|
|
17
|
+
And I click the retry opener
|
|
18
|
+
And I click "Retry processing"
|
|
19
|
+
Then the frame should render the processing state
|
|
20
|
+
And the page should not have navigated away
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
Feature: The retry affordance is gated by ActiveStorage::AsyncVariants.retry_visible_if
|
|
2
|
+
|
|
3
|
+
Scenario: When retry_visible_if returns true, the retry chrome renders
|
|
4
|
+
Given a user with an attached avatar
|
|
5
|
+
And the retry affordance is visible to everyone
|
|
6
|
+
And the avatar's :thumb_proc variant is in failed state with error "boom"
|
|
7
|
+
When I visit the avatar page for the :thumb_proc variant
|
|
8
|
+
Then the retry affordance should be visible
|
|
9
|
+
|
|
10
|
+
Scenario: When retry_visible_if returns false, the retry chrome is omitted
|
|
11
|
+
Given a user with an attached avatar
|
|
12
|
+
And the retry affordance is hidden
|
|
13
|
+
And the avatar's :thumb_proc variant is in failed state with error "boom"
|
|
14
|
+
When I visit the avatar page for the :thumb_proc variant
|
|
15
|
+
Then the retry affordance should NOT be visible
|
|
16
|
+
|
|
17
|
+
Scenario: A proc that consults current_user gates the affordance per-viewer
|
|
18
|
+
Given a user with an attached avatar
|
|
19
|
+
And the retry affordance requires a signed-in user
|
|
20
|
+
And the avatar's :thumb_proc variant is in failed state with error "boom"
|
|
21
|
+
When I sign in as the user
|
|
22
|
+
And I visit the avatar page for the :thumb_proc variant
|
|
23
|
+
Then the retry affordance should be visible
|
|
@@ -15,6 +15,7 @@ Feature: image_tag with async: true emits the right markup per variant state
|
|
|
15
15
|
|
|
16
16
|
Scenario: A failed variant renders the failed partial inside the frame
|
|
17
17
|
Given a user with an attached avatar
|
|
18
|
+
And the retry affordance is visible to everyone
|
|
18
19
|
And the avatar's :thumb_proc variant is in failed state with error "boom"
|
|
19
20
|
When I visit the avatar page for the :thumb_proc variant
|
|
20
21
|
Then the page should contain a turbo-frame
|
data/gemfiles/rails_7.2.gemfile
CHANGED
data/gemfiles/rails_8.0.gemfile
CHANGED
data/gemfiles/rails_8.1.gemfile
CHANGED
|
@@ -33,10 +33,7 @@ module ActiveStorage
|
|
|
33
33
|
assert_async_variant!(variant)
|
|
34
34
|
|
|
35
35
|
if async && !async_variant_processed_inline?(variant)
|
|
36
|
-
async_variant_turbo_frame(variant, kind:, direct:, html_options: options)
|
|
37
|
-
# populate TurboFrame with a placeholder image/video, so there's valid markup right away
|
|
38
|
-
yield [async_variant_representation_path(variant), *rest], options.except(:data)
|
|
39
|
-
end
|
|
36
|
+
async_variant_turbo_frame(variant, kind:, direct:, html_options: options)
|
|
40
37
|
else
|
|
41
38
|
yield [async_variant_resolved_src(variant, direct:), *rest], options
|
|
42
39
|
end
|
|
@@ -48,13 +45,13 @@ module ActiveStorage
|
|
|
48
45
|
end
|
|
49
46
|
end
|
|
50
47
|
|
|
51
|
-
def async_variant_turbo_frame(variant, kind:, direct:, html_options
|
|
48
|
+
def async_variant_turbo_frame(variant, kind:, direct:, html_options:)
|
|
52
49
|
content_tag(
|
|
53
50
|
:"turbo-frame",
|
|
51
|
+
"",
|
|
54
52
|
id: async_variant_frame_id(variant),
|
|
55
53
|
src: async_variant_frame_src(variant, kind: kind, direct: direct, html_options: html_options),
|
|
56
54
|
refresh: "morph",
|
|
57
|
-
&block
|
|
58
55
|
)
|
|
59
56
|
end
|
|
60
57
|
end
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require "turbo-rails"
|
|
4
|
+
require "isolate_assets"
|
|
4
5
|
require_relative "async_variants/version"
|
|
5
6
|
require_relative "async_variants/helper"
|
|
6
7
|
require_relative "async_variants/transformer"
|
|
@@ -22,12 +23,24 @@ module ActiveStorage
|
|
|
22
23
|
PASS_THROUGH_HTML_OPTIONS = %i[alt width height controls autoplay preload].freeze
|
|
23
24
|
|
|
24
25
|
mattr_accessor :cdn_host
|
|
26
|
+
mattr_accessor :retry_visible_proc, default: ->(_view) { false }
|
|
25
27
|
|
|
26
28
|
# Lets the host app plug its auth chain (and thus `current_user`) into the
|
|
27
29
|
# gem's StatesController. Set to a string so resolution is deferred until
|
|
28
30
|
# the host's class is autoloadable. Defaults to ActionController::Base.
|
|
29
31
|
mattr_accessor :parent_controller, default: "ActionController::Base"
|
|
30
32
|
|
|
33
|
+
# Gates the failed-state retry affordance; the block runs in the view context.
|
|
34
|
+
def self.retry_visible_if(&block)
|
|
35
|
+
self.retry_visible_proc = block
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def self.retry_visible?(view)
|
|
39
|
+
!!view.instance_exec(&retry_visible_proc)
|
|
40
|
+
rescue StandardError
|
|
41
|
+
false
|
|
42
|
+
end
|
|
43
|
+
|
|
31
44
|
class Engine < ::Rails::Engine
|
|
32
45
|
# Prepend the core model/reflection extensions before eager_load runs
|
|
33
46
|
# so that models' has_X_attached blocks (and the Variation.wrap calls
|
|
@@ -79,6 +92,8 @@ module ActiveStorage
|
|
|
79
92
|
)
|
|
80
93
|
end
|
|
81
94
|
|
|
95
|
+
Assets = IsolateAssets.register(namespace: self, engine: Engine, route_name: :async_variant_asset)
|
|
96
|
+
|
|
82
97
|
def self.callback_token_for(variant_record)
|
|
83
98
|
ActiveStorage.verifier.generate(variant_record.id, purpose: :async_variant_callback)
|
|
84
99
|
end
|
metadata
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: active_storage-async_variants
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.5.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Micah Geisel
|
|
8
8
|
bindir: exe
|
|
9
9
|
cert_chain: []
|
|
10
|
-
date: 2026-06-
|
|
10
|
+
date: 2026-06-07 00:00:00.000000000 Z
|
|
11
11
|
dependencies:
|
|
12
12
|
- !ruby/object:Gem::Dependency
|
|
13
13
|
name: activestorage
|
|
@@ -37,6 +37,20 @@ dependencies:
|
|
|
37
37
|
- - ">="
|
|
38
38
|
- !ruby/object:Gem::Version
|
|
39
39
|
version: '2.0'
|
|
40
|
+
- !ruby/object:Gem::Dependency
|
|
41
|
+
name: isolate_assets
|
|
42
|
+
requirement: !ruby/object:Gem::Requirement
|
|
43
|
+
requirements:
|
|
44
|
+
- - ">="
|
|
45
|
+
- !ruby/object:Gem::Version
|
|
46
|
+
version: '0.4'
|
|
47
|
+
type: :runtime
|
|
48
|
+
prerelease: false
|
|
49
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
50
|
+
requirements:
|
|
51
|
+
- - ">="
|
|
52
|
+
- !ruby/object:Gem::Version
|
|
53
|
+
version: '0.4'
|
|
40
54
|
email:
|
|
41
55
|
- micah@botandrose.com
|
|
42
56
|
executables: []
|
|
@@ -52,6 +66,8 @@ files:
|
|
|
52
66
|
- LICENSE.txt
|
|
53
67
|
- README.md
|
|
54
68
|
- Rakefile
|
|
69
|
+
- app/assets/javascripts/retry.js
|
|
70
|
+
- app/assets/stylesheets/retry.css
|
|
55
71
|
- app/controllers/active_storage/async_variants/callbacks_controller.rb
|
|
56
72
|
- app/controllers/active_storage/async_variants/states_controller.rb
|
|
57
73
|
- app/views/active_storage/async_variants/states/_failed.html.erb
|
|
@@ -60,6 +76,8 @@ files:
|
|
|
60
76
|
- app/views/active_storage/async_variants/states/_processing.html.erb
|
|
61
77
|
- app/views/active_storage/async_variants/states/show.html.erb
|
|
62
78
|
- config/routes.rb
|
|
79
|
+
- features/retry_flow.feature
|
|
80
|
+
- features/retry_visibility.feature
|
|
63
81
|
- features/state_rendering.feature
|
|
64
82
|
- features/step_definitions/dummy_steps.rb
|
|
65
83
|
- features/support/env.rb
|