studio-engine 0.74.11 → 0.74.12
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 +26 -0
- data/app/views/studio/modals/_host.html.erb +7 -2
- data/app/views/studio/modals/_scoped_host.html.erb +5 -2
- data/app/views/studio/modals/blocks/_card_header.html.erb +12 -6
- data/app/views/studio/modals/blocks/_success_card.html.erb +11 -6
- data/lib/studio/partial_block.rb +50 -0
- data/lib/studio/version.rb +1 -1
- data/lib/studio.rb +1 -0
- metadata +3 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: c4fe6cb6d0167ad680d5d6e9dc577a623c87e83d839847be79950877b922d576
|
|
4
|
+
data.tar.gz: 07d63386f006d3bfba60556910097f0f0797e52f28f6bcde41e38c37e4af9326
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: a46c78e389a2776d874522186127a1a16ae5bca04429ee3879a3bd0b1a8ce4e669d382803d94a2f3becb127c8735cd40255f62ef848cdfec3a76e912997d347d
|
|
7
|
+
data.tar.gz: caab23ce4c8cd6788ad50bc28391302b9b388015dd33e19a80855ba708f335f38653a449af6afd3b4afe43dd92a7cd87233ed51874f831c7cf175d6109ebe76e
|
data/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,32 @@ The format is [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). This pro
|
|
|
4
4
|
|
|
5
5
|
## Unreleased
|
|
6
6
|
|
|
7
|
+
## 0.74.12 — 2026-09-16
|
|
8
|
+
|
|
9
|
+
### Fixed
|
|
10
|
+
|
|
11
|
+
- **A modal partial rendered from a layout no longer re-emits the whole page
|
|
12
|
+
body.** In a Rails partial `block_given?` is always true, and a `yield` with
|
|
13
|
+
no caller block returns the layout's content: the entire rendered page once
|
|
14
|
+
the layout is rendering. Four partials tested their block that way.
|
|
15
|
+
turf-monster hit it live: its layout-rendered username modal reaches
|
|
16
|
+
`blocks/_card_header` with a nil subtitle, so every signed-in page for a user
|
|
17
|
+
allowed to rename carried a second copy of its own body inside a
|
|
18
|
+
`<template>`. Measured on turf-monster's `/`, `/contests` and `/account`
|
|
19
|
+
against 0.74.11: two copies of the page body before, one after.
|
|
20
|
+
- New `Studio::PartialBlock.content(view, yielded)` returns a caller's block
|
|
21
|
+
content, or nil when there is none. It treats a blank yield, or a yield
|
|
22
|
+
equal to `content_for(:layout)`, as no block.
|
|
23
|
+
- `blocks/_card_header` (the subtitle slot), `blocks/_success_card` (both
|
|
24
|
+
slot positions), `_host` and `_scoped_host` (the registration seam) now
|
|
25
|
+
ask it instead of `block_given?`.
|
|
26
|
+
- The block contract is unchanged. A caller passing a non-blank block
|
|
27
|
+
renders as before, so no consumer edits a call site. A no-block render
|
|
28
|
+
outside a layout now omits the empty subtitle paragraph or slot wrapper it
|
|
29
|
+
used to leave.
|
|
30
|
+
- Guarded by `test/views/partial_block_layout_yield_test.rb`, which renders
|
|
31
|
+
each partial through a real layout and counts the page body.
|
|
32
|
+
|
|
7
33
|
## 0.74.11 — 2026-09-13
|
|
8
34
|
|
|
9
35
|
## 0.74.10 — 2026-09-13
|
|
@@ -838,8 +838,13 @@
|
|
|
838
838
|
:class="$store.modals.cardClasses()">
|
|
839
839
|
<%# Consumer-provided content registrations. Each block typically
|
|
840
840
|
contains a <template x-if="$store.modals.current().id === 'X'">
|
|
841
|
-
render "modals/X" </template>.
|
|
842
|
-
|
|
841
|
+
render "modals/X" </template>.
|
|
842
|
+
NOT `yield if block_given?`. In a partial block_given? is always true,
|
|
843
|
+
and a caller-less yield returns the layout's content, so a host
|
|
844
|
+
rendered from a layout with no block (an app whose modals all live in
|
|
845
|
+
_host_extras) put the whole page body inside the card. See
|
|
846
|
+
Studio::PartialBlock. %>
|
|
847
|
+
<%= Studio::PartialBlock.content(self, yield) %>
|
|
843
848
|
|
|
844
849
|
<%# APP-WIDE modal registrations — the second seam. The block above is
|
|
845
850
|
per-CALLSITE: an app rendering this host from two layouts (a live one
|
|
@@ -364,8 +364,11 @@
|
|
|
364
364
|
@keydown.escape.window="$store.<%= scoped_store %>.current() && $store.<%= scoped_store %>.current().props.dismissible !== false && $store.<%= scoped_store %>.close()"
|
|
365
365
|
@click.self="$store.<%= scoped_store %>.current() && $store.<%= scoped_store %>.current().props.dismissible !== false && $store.<%= scoped_store %>.close()">
|
|
366
366
|
<div class="<%= card_class %> max-h-[85dvh] overflow-y-auto" :class="$store.<%= scoped_store %>.cardClasses()">
|
|
367
|
-
<%# Consumer-provided registrations — one <template x-if> per modal id.
|
|
368
|
-
|
|
367
|
+
<%# Consumer-provided registrations — one <template x-if> per modal id.
|
|
368
|
+
Not `yield if block_given?`: a partial's block_given? is always true,
|
|
369
|
+
and a caller-less yield returns the layout's whole page body. See
|
|
370
|
+
Studio::PartialBlock. %>
|
|
371
|
+
<%= Studio::PartialBlock.content(self, yield) %>
|
|
369
372
|
</div>
|
|
370
373
|
</div>
|
|
371
374
|
</template>
|
|
@@ -21,10 +21,16 @@
|
|
|
21
21
|
for processing / loading states.
|
|
22
22
|
size: :md (default) OR :lg (celebration end-state, text-3xl title).
|
|
23
23
|
|
|
24
|
-
Block: if given, the block's content replaces the subtitle area.
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
24
|
+
Block: if given, the block's content replaces the subtitle area. The subtitle
|
|
25
|
+
locals outrank it.
|
|
26
|
+
|
|
27
|
+
NEVER TEST THE BLOCK WITH block_given?. In a Rails partial it is always true,
|
|
28
|
+
and a yield with no caller block returns the LAYOUT's content. This branch
|
|
29
|
+
used to read `elsif block_given?`, so a header rendered from the layout with
|
|
30
|
+
no subtitle yielded the whole page body into the modal: every signed-in
|
|
31
|
+
turf-monster page for a user allowed to rename carried two copies of itself
|
|
32
|
+
(modal-header-yields-whole-page, 2026-09-16). Studio::PartialBlock
|
|
33
|
+
(lib/studio/partial_block.rb) tells a real block from that fallback.
|
|
28
34
|
%>
|
|
29
35
|
<%
|
|
30
36
|
icon_color = local_assigns[:icon_color] || 'primary'
|
|
@@ -77,7 +83,7 @@
|
|
|
77
83
|
<p class="<%= sub_cls %>" x-text="<%= subtitle_key %>"></p>
|
|
78
84
|
<% elsif local_assigns[:subtitle] %>
|
|
79
85
|
<p class="<%= sub_cls %>"><%= subtitle %></p>
|
|
80
|
-
<% elsif
|
|
81
|
-
<p class="<%= sub_cls %>"><%=
|
|
86
|
+
<% elsif (caller_block = Studio::PartialBlock.content(self, yield)) %>
|
|
87
|
+
<p class="<%= sub_cls %>"><%= caller_block %></p>
|
|
82
88
|
<% end %>
|
|
83
89
|
</div>
|
|
@@ -73,6 +73,11 @@
|
|
|
73
73
|
large_title = !!local_assigns[:large_title]
|
|
74
74
|
use_drain = has_redirect && !!local_assigns[:cta_drain]
|
|
75
75
|
slot_position = local_assigns.fetch(:slot_position, :below_cta).to_sym
|
|
76
|
+
# The caller's block, captured ONCE, or nil when there is none. Never
|
|
77
|
+
# block_given?: in a partial it is always true, and a caller-less yield
|
|
78
|
+
# returns the layout's content, so a card rendered from a layout with no
|
|
79
|
+
# block re-emitted the whole page body under its CTA. See Studio::PartialBlock.
|
|
80
|
+
caller_block = Studio::PartialBlock.content(self, yield)
|
|
76
81
|
|
|
77
82
|
data_attr = "{
|
|
78
83
|
_remaining: #{redirect_secs},
|
|
@@ -231,8 +236,8 @@
|
|
|
231
236
|
card has always run its kickoff timer and seeds bar above the button for
|
|
232
237
|
that reason, and this is what lets the engine's card do the same instead of
|
|
233
238
|
the app forking one to keep the order. %>
|
|
234
|
-
<% if
|
|
235
|
-
<div class="mb-4"><%=
|
|
239
|
+
<% if caller_block && slot_position == :above_cta %>
|
|
240
|
+
<div class="mb-4"><%= caller_block %></div>
|
|
236
241
|
<% end %>
|
|
237
242
|
|
|
238
243
|
<%# Primary CTA — three flavors:
|
|
@@ -288,9 +293,9 @@
|
|
|
288
293
|
<% end %>
|
|
289
294
|
|
|
290
295
|
<%# ...or below it, which stays the DEFAULT: every existing caller renders here
|
|
291
|
-
and none of them asked to move.
|
|
292
|
-
one of these two branches
|
|
293
|
-
<% if
|
|
294
|
-
<div class="mt-3"><%=
|
|
296
|
+
and none of them asked to move. The block is captured once at the top, and
|
|
297
|
+
exactly one of these two branches renders it. %>
|
|
298
|
+
<% if caller_block && slot_position != :above_cta %>
|
|
299
|
+
<div class="mt-3"><%= caller_block %></div>
|
|
295
300
|
<% end %>
|
|
296
301
|
</div>
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Studio
|
|
4
|
+
# The content of the block a CALLER passed to a partial, or nil when it passed
|
|
5
|
+
# none. The engine's slot-taking partials ask this, never `block_given?`.
|
|
6
|
+
#
|
|
7
|
+
# WHY block_given? IS THE WRONG QUESTION IN A PARTIAL. ActionView compiles a
|
|
8
|
+
# template into a method and ALWAYS invokes that method with a block:
|
|
9
|
+
# PartialRenderer passes `{ |*name| view._layout_for(*name, &block) }` whether
|
|
10
|
+
# or not the render that reached the partial carried one. So `block_given?` is
|
|
11
|
+
# true in every partial. A `yield` with no caller block then falls through to
|
|
12
|
+
# `_layout_for`'s other branch, the LAYOUT's content. While the page's own
|
|
13
|
+
# template renders, that is empty. Once the layout renders, it is THE WHOLE
|
|
14
|
+
# PAGE BODY.
|
|
15
|
+
#
|
|
16
|
+
# MEASURED 2026-09-16 (modal-header-yields-whole-page). turf-monster renders
|
|
17
|
+
# the modal host from its layout. The username modal's plain "Saved" card
|
|
18
|
+
# reached blocks/_card_header with a nil subtitle, and the header's
|
|
19
|
+
# `elsif block_given?` yielded the entire page a second time inside a
|
|
20
|
+
# <template>: two copies of the page body on /, /contests and /account for
|
|
21
|
+
# every signed-in user allowed to rename. Nothing painted, nothing raised, and
|
|
22
|
+
# every request returned 200.
|
|
23
|
+
#
|
|
24
|
+
# HOW THIS ANSWERS IT. Rails gives a template no way to ask whether its caller
|
|
25
|
+
# passed a block, so the partial yields and hands the result in:
|
|
26
|
+
#
|
|
27
|
+
# caller_block = Studio::PartialBlock.content(self, yield)
|
|
28
|
+
#
|
|
29
|
+
# A caller-less yield can only return the layout's content or nothing, so a
|
|
30
|
+
# yield equal to `content_for(:layout)` is that fallback, not a block. A blank
|
|
31
|
+
# yield also reads as no block: an empty slot wrapper is never what a caller
|
|
32
|
+
# meant.
|
|
33
|
+
#
|
|
34
|
+
# THIS FIXES THE PREDICATE; THE BLOCK CONTRACT IS UNCHANGED. A caller that
|
|
35
|
+
# passes a non-blank block renders exactly as before, so no consumer edits a
|
|
36
|
+
# call site. A NEW slot should still prefer a named local that names a partial
|
|
37
|
+
# (solana-studio's wallet picker does), which needs no predicate at all.
|
|
38
|
+
module PartialBlock
|
|
39
|
+
module_function
|
|
40
|
+
|
|
41
|
+
# view — the partial's view context (`self` inside the template).
|
|
42
|
+
# yielded — the partial's own `yield`, evaluated where the partial calls this.
|
|
43
|
+
def content(view, yielded)
|
|
44
|
+
return nil if yielded.blank?
|
|
45
|
+
return nil if yielded == view.content_for(:layout)
|
|
46
|
+
|
|
47
|
+
yielded
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
data/lib/studio/version.rb
CHANGED
data/lib/studio.rb
CHANGED
|
@@ -10,6 +10,7 @@ require "studio/theme_resolver"
|
|
|
10
10
|
require "studio/ui_primitives"
|
|
11
11
|
require "studio/js_literal"
|
|
12
12
|
require "studio/js_identifier"
|
|
13
|
+
require "studio/partial_block"
|
|
13
14
|
require "studio/sidebar_sections"
|
|
14
15
|
require "studio/profile_sections"
|
|
15
16
|
require "studio/profile_image"
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: studio-engine
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.74.
|
|
4
|
+
version: 0.74.12
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Alex McRitchie
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-09-
|
|
11
|
+
date: 2026-09-17 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: rails
|
|
@@ -570,6 +570,7 @@ files:
|
|
|
570
570
|
- lib/studio/name_parts.rb
|
|
571
571
|
- lib/studio/newsletter.rb
|
|
572
572
|
- lib/studio/oauth_identity.rb
|
|
573
|
+
- lib/studio/partial_block.rb
|
|
573
574
|
- lib/studio/profile_image.rb
|
|
574
575
|
- lib/studio/profile_sections.rb
|
|
575
576
|
- lib/studio/redis.rb
|