spree_core 5.6.0.rc2 → 5.6.0.rc3

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: df2eef5a82cf40bca9b5c518b663153584a72dc1dadf05880a5c2ce344766394
4
- data.tar.gz: 48771116e279ccb8c5ed4f83896a60e8063b74cb8785ff51953c9d8446a804f2
3
+ metadata.gz: 45a9c3a76133036f89b3eabe73c9e53cd57dfd6417ee32211f88335424b9da94
4
+ data.tar.gz: f409d6453ba2a5a6fa9f66c3b26ffa8686c05723bcae99110cb7eede6d3fedb1
5
5
  SHA512:
6
- metadata.gz: 3a27318377cb47ae53d0405a9a034642119446d4507f03c68966cd550cf41430dcdb0cc3ff7c67f8bf1435b695816c26f92b73e525f2ead42eaf83070c019f40
7
- data.tar.gz: 1ac22bf54e39352eede750b630b7e2d6ca6a0d4713c776f0efd20e9cf552f3689b24b154c0694db3ae0edabba37a0bc1c7ad91aa5a0e84cca62aa5cc459d48f7
6
+ metadata.gz: 596464be183b137236eb76e984a2b8d84091ea95e22a553d7ffb3ab0a5bc3055d380d1283946517ad20e3a8bbd3617d9a246ebbe2157923943e64b054a014aaa
7
+ data.tar.gz: 58e3bb7aed929b0d17ac96510dee26475cdcfc645b49e491fa38625c958d5228ef81e5f9d703488dbe576167e2ef8c2078d993cce6a37feaad714f62c0b6b8c6
@@ -0,0 +1,37 @@
1
+ module Spree
2
+ # Converts stored rich text HTML (Tiptap output persisted in plain text
3
+ # columns, e.g. +Spree::Product#description+) into a faithful plain-text
4
+ # rendering for API +description+/+body+ fields.
5
+ #
6
+ # Tiptap serializes blocks with no whitespace between them
7
+ # (+<p>a</p><p>b</p>+), so a naive tag strip glues adjacent blocks into a
8
+ # single run ("ab") and loses every paragraph and line break. This maps
9
+ # block boundaries and +<br>+ to newlines before stripping tags, keeping the
10
+ # text readable for storefronts, feeds, search indexing, and meta tags.
11
+ #
12
+ # Newlines already present in the markup (pretty-printed source from the
13
+ # legacy admin's TinyMCE code view, imports, seeds) render as a single space
14
+ # in a browser and are treated the same way here — only +<br>+ and block
15
+ # boundaries produce line breaks.
16
+ module RichTextHelper
17
+ # Closing block tags whose boundaries become line breaks.
18
+ BLOCK_BOUNDARY = %r{</(?:p|div|li|h[1-6]|blockquote|tr|ul|ol|table|section|article|header|footer|pre)>}i
19
+
20
+ # Hard line breaks.
21
+ LINE_BREAK = %r{<br\s*/?>}i
22
+
23
+ # @param html [String, nil] rich text HTML
24
+ # @return [String] plain text with paragraph and line breaks preserved
25
+ def self.to_plain_text(html)
26
+ return '' if html.blank?
27
+
28
+ with_breaks = html.gsub(/\s+/, ' ').gsub(LINE_BREAK, "\n").gsub(BLOCK_BOUNDARY, "\\0\n")
29
+
30
+ Nokogiri::HTML.fragment(with_breaks).text
31
+ .gsub(/[^\S\n]+/, ' ') # collapse runs of non-newline whitespace
32
+ .gsub(/ *\n */, "\n") # trim spaces hugging newlines
33
+ .gsub(/\n{3,}/, "\n\n") # cap consecutive blank lines
34
+ .strip
35
+ end
36
+ end
37
+ end
@@ -1,6 +1,22 @@
1
1
  module Spree
2
2
  module Imports
3
3
  class ProcessGroupJob < Spree::Imports::BaseJob
4
+ # A large import fans out into many group jobs; without a cap they
5
+ # occupy every worker thread as one frees up, starving checkout-critical
6
+ # queues that share the pool. Cap concurrent groups per import at 75%
7
+ # of JOB_THREADS (at least 1), or SPREE_IMPORT_JOB_CONCURRENCY when set
8
+ # (0 disables the cap) — excess jobs wait as blocked executions, not in
9
+ # worker threads. Both values are read by the process enqueueing the
10
+ # import, so on a split web/worker deployment set the explicit override
11
+ # on the web service. The semaphore duration must outlive the slowest
12
+ # group; expiry just lifts the cap, it doesn't lose jobs. Solid
13
+ # Queue-specific (no-op on other adapters).
14
+ import_concurrency = ENV['SPREE_IMPORT_JOB_CONCURRENCY'].presence&.to_i ||
15
+ [Integer(ENV.fetch('JOB_THREADS', 3)) * 3 / 4, 1].max
16
+ if import_concurrency.positive? && respond_to?(:limits_concurrency)
17
+ limits_concurrency to: import_concurrency, key: ->(import_id, _row_ids) { import_id }, duration: 30.minutes
18
+ end
19
+
4
20
  # Rows are loaded in slices so a large group never holds every ImportRow
5
21
  # (and its raw CSV data) in memory for the whole job.
6
22
  ROWS_BATCH_SIZE = 100
@@ -12,6 +28,9 @@ module Spree
12
28
  mappings = import.mappings.mapped.to_a
13
29
  schema_fields = import.schema_fields
14
30
  large = import.large_import?
31
+ grouped = import.group_column.present? && mappings.any? { |m| m.schema_field == import.group_column }
32
+ started_at = Time.current
33
+ processed_rows = []
15
34
 
16
35
  row_ids.each_slice(ROWS_BATCH_SIZE) do |ids|
17
36
  # Skip rows already completed on a prior attempt so retries don't double-process them.
@@ -25,18 +44,45 @@ module Spree
25
44
  Spree::Events.disable do
26
45
  rows.each { |row| row.bulk_process!(mappings: mappings, schema_fields: schema_fields) }
27
46
  end
47
+ elsif grouped
48
+ # A group is one product plus its variants: per-record lifecycle
49
+ # events (variant.created, price.created, product.updated per
50
+ # touch) are noise to subscribers — one product event is published
51
+ # for the whole group below. import_row.* events still flow.
52
+ Spree::Events.disable_lifecycle do
53
+ rows.each { |row| row.process!(mappings: mappings, schema_fields: schema_fields) }
54
+ end
28
55
  else
29
56
  rows.each do |row|
30
57
  row.process!(mappings: mappings, schema_fields: schema_fields)
31
58
  end
32
59
  end
60
+ processed_rows.concat(rows)
33
61
  end
34
62
 
63
+ publish_group_events(processed_rows, started_at) if grouped
35
64
  check_import_completion(import, large)
36
65
  end
37
66
 
38
67
  private
39
68
 
69
+ # One event per product touched by this group: `product.created` when the
70
+ # product came into existence during this run, `product.updated` otherwise
71
+ # (including retries of a group whose first attempt created it).
72
+ def publish_group_events(rows, started_at)
73
+ products = rows.select { |row| row.status == 'completed' }.filter_map do |row|
74
+ item = row.item
75
+ next item if item.is_a?(Spree::Product)
76
+
77
+ item.product if item.respond_to?(:product)
78
+ end.uniq
79
+
80
+ products.each do |product|
81
+ event = product.created_at >= started_at ? 'product.created' : 'product.updated'
82
+ product.publish_event(event)
83
+ end
84
+ end
85
+
40
86
  # Completion is row-state-derived so retry-induced over-increments of the counter
41
87
  # stay harmless. The counter pre-check just shortcuts the row scan for workers
42
88
  # that obviously can't be the last group to finish. `in_flight` excludes orphaned
@@ -39,6 +39,18 @@ module Spree
39
39
  included do
40
40
  class_attribute :publish_events, default: true
41
41
  class_attribute :lifecycle_events_enabled, default: false
42
+
43
+ after_update :mark_real_update_for_events
44
+ end
45
+
46
+ # A bare `touch` (variant → product, price → variant, asset → variants)
47
+ # writes no attribute changes — the resulting update event would carry
48
+ # nothing subscribers haven't already been told. Track it here so the
49
+ # commit callback can tell a touch-only commit from a real update;
50
+ # `saved_changes` can't (a touch replays the previous save's changeset).
51
+ def touch(*names, time: nil)
52
+ @_spree_touched_for_events = true
53
+ super
42
54
  end
43
55
 
44
56
  class_methods do
@@ -228,7 +240,21 @@ module Spree
228
240
  end
229
241
 
230
242
  def should_publish_events?
231
- self.class.publish_events && Spree::Events.enabled?
243
+ self.class.publish_events && Spree::Events.lifecycle_enabled?
244
+ end
245
+
246
+ # True when this commit was caused only by `touch` — no real save ran
247
+ # (`after_update` fires for saves, never for touches). Flags reset every
248
+ # commit so a later genuine update publishes normally.
249
+ def touch_only_update?
250
+ touched = @_spree_touched_for_events
251
+ updated = @_spree_updated_for_events
252
+ @_spree_touched_for_events = @_spree_updated_for_events = nil
253
+ touched && !updated
254
+ end
255
+
256
+ def mark_real_update_for_events
257
+ @_spree_updated_for_events = true
232
258
  end
233
259
 
234
260
  def publish_create_event
@@ -236,6 +262,8 @@ module Spree
236
262
  end
237
263
 
238
264
  def publish_update_event
265
+ return if touch_only_update?
266
+
239
267
  publish_event("#{event_prefix}.updated")
240
268
  end
241
269
 
@@ -233,6 +233,7 @@ module Spree
233
233
  assign_new_default_address_to_user
234
234
 
235
235
  if can_be_deleted?
236
+ unassign_from_incomplete_orders
236
237
  super
237
238
  else
238
239
  update_column :deleted_at, Time.current
@@ -364,5 +365,11 @@ module Spree
364
365
  def assign_new_default_address_to_user_scope
365
366
  user.addresses.not_quick_checkout.reorder(created_at: :desc)
366
367
  end
368
+
369
+ def unassign_from_incomplete_orders
370
+ orders = Spree::Order.incomplete.where(user_id: user_id)
371
+ orders.where(ship_address_id: id).update_all(ship_address_id: nil, state: 'address', updated_at: Time.current)
372
+ orders.where(bill_address_id: id).update_all(bill_address_id: nil, state: 'address', updated_at: Time.current)
373
+ end
367
374
  end
368
375
  end
@@ -24,11 +24,21 @@ module Spree
24
24
  end
25
25
 
26
26
  # When it comes to computing shipments or line items: same same.
27
+ #
28
+ # Both tax modes are computed from the item's live taxable basis, which
29
+ # follows every discount: item-level promotions and the item's share of
30
+ # whole-order promotions (see LineItem#taxable_basis). Included
31
+ # (VAT-style) tax intentionally does not read the stored pre_tax_amount
32
+ # column — that column is only refreshed by Spree::TaxRate.adjust on
33
+ # checkout transitions, so it goes stale the moment a promotion is
34
+ # applied or removed.
27
35
  def compute_shipment_or_line_item(item)
36
+ basis = item.taxable_basis
37
+
28
38
  if rate.included_in_price
29
- deduced_total_by_rate(item.pre_tax_amount, rate)
39
+ deduced_total_by_rate(net_basis(item, basis), rate)
30
40
  else
31
- round_to_two_places(item.discounted_amount * rate.amount)
41
+ round_to_two_places(basis * rate.amount)
32
42
  end
33
43
  end
34
44
 
@@ -54,5 +64,24 @@ module Spree
54
64
  def deduced_total_by_rate(pre_tax_amount, rate)
55
65
  round_to_two_places(pre_tax_amount * rate.amount)
56
66
  end
67
+
68
+ # Backs all included-in-price rates out of the gross basis, mirroring
69
+ # Spree::TaxRate.store_pre_tax_amount (several rates can share a tax
70
+ # category, e.g. MOSS VAT). Falls back to this rate's amount when no
71
+ # rate matches the item, e.g. in bare setups without matching zones.
72
+ def net_basis(item, basis)
73
+ included = included_rates_amount(item)
74
+ included = rate.amount if included.zero?
75
+
76
+ basis / (1 + included)
77
+ end
78
+
79
+ # Memoized per tax category: tax rate and zone rows are configuration,
80
+ # invariant within a recalculation pass.
81
+ def included_rates_amount(item)
82
+ @included_rates_amount ||= {}
83
+ @included_rates_amount[item.tax_category_id] ||=
84
+ Spree::TaxRate.included_tax_amount_for(tax_zone: rate.zone, tax_category: item.tax_category)
85
+ end
57
86
  end
58
87
  end
@@ -146,6 +146,29 @@ module Spree
146
146
  alias discounted_money display_discounted_amount
147
147
  alias discounted_amount taxable_amount
148
148
 
149
+ # Returns the amount this line item is taxed on: the discounted amount
150
+ # reduced further by the line item's proportional share of any
151
+ # whole-order promotions, which are adjustments on the order itself and
152
+ # therefore not part of +taxable_adjustment_total+. Never negative.
153
+ #
154
+ # @return [BigDecimal]
155
+ def taxable_basis
156
+ basis = taxable_amount
157
+ return basis if basis <= 0
158
+
159
+ order_discount = order.order_level_promo_total
160
+ return basis if order_discount.zero?
161
+
162
+ # summed in SQL so the allocation uses the persisted adjustment totals
163
+ # of all line items, not a possibly stale cached association
164
+ items_total = order.line_items.reorder(nil).pick(
165
+ Arel.sql('COALESCE(SUM(price * quantity + taxable_adjustment_total), 0)')
166
+ )
167
+ return basis if items_total <= 0
168
+
169
+ [basis + (order_discount * basis / items_total), BigDecimal(0)].max
170
+ end
171
+
149
172
  # Returns the final amount of the line item
150
173
  #
151
174
  # @return [BigDecimal]
@@ -360,6 +360,16 @@ module Spree
360
360
  line_items.sum(:pre_tax_amount)
361
361
  end
362
362
 
363
+ # Sum of the eligible promotion adjustments applied to the order itself
364
+ # (whole-order discounts created by Promotion::Actions::CreateAdjustment),
365
+ # as opposed to promotions applied to individual line items or shipments.
366
+ # Zero or negative.
367
+ #
368
+ # @return [BigDecimal]
369
+ def order_level_promo_total
370
+ adjustments.promotion.eligible.sum(:amount)
371
+ end
372
+
363
373
  # Sum of all line item and shipment pre-tax
364
374
  def pre_tax_total
365
375
  pre_tax_item_amount + shipments.sum(:pre_tax_amount)
@@ -207,6 +207,15 @@ module Spree
207
207
  end
208
208
  alias discounted_amount discounted_cost
209
209
 
210
+ # Returns the amount this shipment is taxed on: its discounted cost,
211
+ # never negative (stacked shipping promotions can push discounted_cost
212
+ # below zero). Whole-order promotions are not allocated to shipments.
213
+ #
214
+ # @return [BigDecimal]
215
+ def taxable_basis
216
+ [discounted_cost, BigDecimal(0)].max
217
+ end
218
+
210
219
  def final_price
211
220
  cost + adjustment_total
212
221
  end
@@ -54,6 +54,13 @@ module Spree
54
54
  # Pre-tax amounts must be stored so that we can calculate
55
55
  # correct rate amounts in the future. For example:
56
56
  # https://github.com/spree/spree/issues/4318#issuecomment-34723428
57
+ #
58
+ # NOTE: this deliberately excludes the item's share of whole-order
59
+ # promotions and is therefore NOT the basis tax is computed on (that is
60
+ # LineItem#taxable_basis / Shipment#taxable_basis). Refund and reporting
61
+ # code (e.g. Calculator::Returns::DefaultRefundAmount) weighs order-level
62
+ # adjustments separately on top of this column, so allocating them here
63
+ # would double-count discounts.
57
64
  def self.store_pre_tax_amount(item, rates)
58
65
  pre_tax_amount = case item.class.to_s
59
66
  when 'Spree::LineItem' then item.discounted_amount
@@ -7,7 +7,16 @@ module Spree
7
7
  attr_accessor :country
8
8
 
9
9
  def call(address:, address_params:, **opts)
10
- order = opts[:order]
10
+ ApplicationRecord.transaction do
11
+ perform(address: address, address_params: address_params, **opts)
12
+ end
13
+ rescue ActiveRecord::RecordInvalid, ActiveRecord::RecordNotDestroyed => e
14
+ failure(e.record)
15
+ end
16
+
17
+ private
18
+
19
+ def perform(address:, address_params:, **opts)
11
20
  default_billing = address_params.key?(:is_default_billing) ? address_params.delete(:is_default_billing) : opts.fetch(:default_billing, false)
12
21
  default_shipping = address_params.key?(:is_default_shipping) ? address_params.delete(:is_default_shipping) : opts.fetch(:default_shipping, false)
13
22
  address_changes_except = opts.fetch(:address_changes_except, [])
@@ -35,24 +44,23 @@ module Spree
35
44
  return success(address) unless address_changed
36
45
 
37
46
  if address.editable?
38
- if address.update(address_params)
39
- if address.user.present?
40
- assign_to_user_as_default(
41
- user: address.user,
42
- address_id: address.id,
43
- default_billing: default_billing,
44
- default_shipping: default_shipping
45
- )
46
- end
47
-
48
- order.update(state: 'address') if order.present?
49
-
50
- success(address)
51
- else
52
- failure(address)
47
+ address.update!(address_params)
48
+
49
+ if address.user.present?
50
+ assign_to_user_as_default(
51
+ user: address.user,
52
+ address_id: address.id,
53
+ default_billing: default_billing,
54
+ default_shipping: default_shipping
55
+ )
53
56
  end
57
+
58
+ reassign_incomplete_orders(address.id, address)
59
+
60
+ success(address)
54
61
  elsif new_address(address_params).valid?
55
- address.destroy
62
+ old_address_id = address.id
63
+ address.destroy!
56
64
 
57
65
  if new_address.user.present?
58
66
  default_billing = address.user_default_billing? || default_billing
@@ -66,12 +74,7 @@ module Spree
66
74
  )
67
75
  end
68
76
 
69
- if order.present?
70
- order.ship_address = new_address if order.ship_address_id == address.id
71
- order.bill_address = new_address if order.bill_address_id == address.id
72
- order.state = 'address'
73
- order.save
74
- end
77
+ reassign_incomplete_orders(old_address_id, new_address)
75
78
 
76
79
  success(new_address)
77
80
  else
@@ -79,8 +82,6 @@ module Spree
79
82
  end
80
83
  end
81
84
 
82
- private
83
-
84
85
  def prepare_address_params!(address, address_params)
85
86
  address_params[:user_id] = address&.user_id
86
87
  address_params[:country_id] ||= address.country_id
@@ -88,6 +89,12 @@ module Spree
88
89
  address_params.transform_values!(&:presence)
89
90
  end
90
91
 
92
+ def reassign_incomplete_orders(old_address_id, new_address)
93
+ orders = Spree::Order.incomplete.where(user_id: new_address.user_id)
94
+ orders.where(ship_address_id: old_address_id).update_all(ship_address_id: new_address.id, state: 'address', updated_at: Time.current)
95
+ orders.where(bill_address_id: old_address_id).update_all(bill_address_id: new_address.id, state: 'address', updated_at: Time.current)
96
+ end
97
+
91
98
  def defaults_changed?(address, default_billing, default_shipping)
92
99
  user = address.user
93
100
 
@@ -75,6 +75,7 @@ development:
75
75
  test:
76
76
  adapter: sqlite3
77
77
  database: db/spree_test<%%= ENV['TEST_ENV_NUMBER'] %>.sqlite3
78
+ timeout: 10000
78
79
  production:
79
80
  adapter: sqlite3
80
81
  database: db/spree_production.sqlite3
@@ -1,5 +1,5 @@
1
1
  module Spree
2
- VERSION = '5.6.0.rc2'.freeze
2
+ VERSION = '5.6.0.rc3'.freeze
3
3
 
4
4
  def self.version
5
5
  VERSION
data/lib/spree/events.rb CHANGED
@@ -187,7 +187,7 @@ module Spree
187
187
  #
188
188
  # @return [Boolean]
189
189
  def enabled?
190
- !@globally_disabled && !Thread.current[:spree_events_disabled]
190
+ !@globally_disabled && !RequestStore.store[:spree_events_disabled]
191
191
  end
192
192
 
193
193
  # Temporarily disable events within a block
@@ -202,11 +202,35 @@ module Spree
202
202
  # end
203
203
  #
204
204
  def disable
205
- previous = Thread.current[:spree_events_disabled]
206
- Thread.current[:spree_events_disabled] = true
205
+ previous = RequestStore.store[:spree_events_disabled]
206
+ RequestStore.store[:spree_events_disabled] = true
207
207
  yield
208
208
  ensure
209
- Thread.current[:spree_events_disabled] = previous
209
+ RequestStore.store[:spree_events_disabled] = previous
210
+ end
211
+
212
+ # Check if automatic lifecycle events (*.created/updated/deleted) are enabled
213
+ #
214
+ # @return [Boolean]
215
+ def lifecycle_enabled?
216
+ enabled? && !RequestStore.store[:spree_lifecycle_events_disabled]
217
+ end
218
+
219
+ # Temporarily disable automatic lifecycle events within a block
220
+ #
221
+ # Unlike {disable}, explicitly published events (`publish_event`) still
222
+ # flow — only the per-record *.created/updated/deleted callbacks are
223
+ # suppressed. Used by bulk operations that emit their own coarser events
224
+ # afterwards (e.g. one product event per imported group).
225
+ #
226
+ # @yield Block during which lifecycle events are disabled
227
+ # @return [Object] Return value of the block
228
+ def disable_lifecycle
229
+ previous = RequestStore.store[:spree_lifecycle_events_disabled]
230
+ RequestStore.store[:spree_lifecycle_events_disabled] = true
231
+ yield
232
+ ensure
233
+ RequestStore.store[:spree_lifecycle_events_disabled] = previous
210
234
  end
211
235
 
212
236
  # Globally disable events
@@ -233,13 +257,13 @@ module Spree
233
257
  #
234
258
  def enable
235
259
  previous_global = @globally_disabled
236
- previous_thread = Thread.current[:spree_events_disabled]
260
+ previous_thread = RequestStore.store[:spree_events_disabled]
237
261
  @globally_disabled = false
238
- Thread.current[:spree_events_disabled] = false
262
+ RequestStore.store[:spree_events_disabled] = false
239
263
  yield
240
264
  ensure
241
265
  @globally_disabled = previous_global
242
- Thread.current[:spree_events_disabled] = previous_thread
266
+ RequestStore.store[:spree_events_disabled] = previous_thread
243
267
  end
244
268
 
245
269
  # Globally enable events
@@ -21,6 +21,18 @@ shared_examples_for 'lifecycle events' do |factory: nil, event_prefix: nil|
21
21
  expect(record).to receive(:publish_event).with("#{lifecycle_event_prefix}.updated")
22
22
  allow(record).to receive(:publish_event).with(anything)
23
23
 
24
+ # update_attribute, not update!: Spree::Shipment#update! shadows the
25
+ # Active Record method with the legacy state-recalculation API.
26
+ Timecop.travel(1.minute.from_now) do
27
+ record.update_attribute(:updated_at, Time.current)
28
+ end
29
+ end
30
+
31
+ it 'does not publish updated event on a bare touch' do
32
+ record = create(lifecycle_factory)
33
+ expect(record).not_to receive(:publish_event).with("#{lifecycle_event_prefix}.updated")
34
+ allow(record).to receive(:publish_event).with(anything)
35
+
24
36
  record.touch
25
37
  end
26
38
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: spree_core
3
3
  version: !ruby/object:Gem::Version
4
- version: 5.6.0.rc2
4
+ version: 5.6.0.rc3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Sean Schofield
@@ -10,7 +10,7 @@ authors:
10
10
  autorequire:
11
11
  bindir: bin
12
12
  cert_chain: []
13
- date: 2026-07-17 00:00:00.000000000 Z
13
+ date: 2026-07-19 00:00:00.000000000 Z
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: i18n-tasks
@@ -849,6 +849,7 @@ files:
849
849
  - app/helpers/spree/locale_helper.rb
850
850
  - app/helpers/spree/localized_names_helper.rb
851
851
  - app/helpers/spree/products_helper.rb
852
+ - app/helpers/spree/rich_text_helper.rb
852
853
  - app/helpers/spree/shipment_helper.rb
853
854
  - app/javascript/spree/core/controllers/address_autocomplete_controller.js
854
855
  - app/javascript/spree/core/controllers/address_form_controller.js
@@ -1860,9 +1861,9 @@ licenses:
1860
1861
  - BSD-3-Clause
1861
1862
  metadata:
1862
1863
  bug_tracker_uri: https://github.com/spree/spree/issues
1863
- changelog_uri: https://github.com/spree/spree/releases/tag/v5.6.0.rc2
1864
+ changelog_uri: https://github.com/spree/spree/releases/tag/v5.6.0.rc3
1864
1865
  documentation_uri: https://docs.spreecommerce.org/
1865
- source_code_uri: https://github.com/spree/spree/tree/v5.6.0.rc2
1866
+ source_code_uri: https://github.com/spree/spree/tree/v5.6.0.rc3
1866
1867
  post_install_message:
1867
1868
  rdoc_options: []
1868
1869
  require_paths: