studio-engine 0.58.1 → 0.59.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 349b589e663f401c61c9d310aaf72e9f86dc63c5dc77cb8ef90ff36904d3ea94
4
- data.tar.gz: 614fb35d60924ca44cb8c8f89b0acf48b821f3a325728048c3cc141983976155
3
+ metadata.gz: 47709f3ad708764cdbf14ab7bb8f82d3698180f201a123e7ec097363ec5f260b
4
+ data.tar.gz: 1fc08166c64c745d19c2bf9c640aac8ecac5726902fa4157a136fd8ddb0ed821
5
5
  SHA512:
6
- metadata.gz: e95e3ead35e2e1e91b22660e8fb4b1beb4509681129c56358c74e20bf8d460bb39f1edbca7ce787789ef7f38d8cf8ecfebc9372e4e417f749a38ea806bf1b279
7
- data.tar.gz: d8eb124ab7bdba7b5fbeb6f2b90c56f6528c6e65be7db24768a3f709daf300d8574124837936ac65269722252bf97e40ab5e34f57a9cb06eace380d3ace1b0d2
6
+ metadata.gz: 300e04e693d5cba7a50d38abd445f71163e3e24b35f693f498e5a167442b389a624fcd3a618231701cd4d116b9a248dd8e1b3279b5ea5fce6d688a3fb8b083ec
7
+ data.tar.gz: c93897b51c64938d0446f73038c0273a815fb761a357c53b54d615e7e11ae3eb0ca4138a8ea918b3fd05fe4053ac58c64445e4cc2fdc0aa5dc154b7828768a2d
data/CHANGELOG.md CHANGED
@@ -188,6 +188,49 @@ The format is [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). This pro
188
188
 
189
189
  ### Fixed
190
190
 
191
+ - **The Geo signpost now reaches every app whose admin chrome the engine owns.**
192
+ The row added last release went into `components/_admin_dropdown` alone, on the
193
+ premise that the shared dropdown reaches every app from one change. It does
194
+ not, and counted against the apps that actually consume this engine it reached
195
+ **none of them**. Wherever a host declares an admin-flagged sidebar section,
196
+ `studio_sidebar_replaces_admin_menu?` SUPPRESSES the dropdown and the link
197
+ sidebar is the admin menu — that is both `mcritchie-studio` and
198
+ `mcritchie-industries`. An app that forks its navbar outright renders neither,
199
+ which is `turf-monster`. Nobody was left on the plain engine navbar to see it,
200
+ so the app carrying the largest admin surface in the ecosystem was among those
201
+ guaranteed never to.
202
+
203
+ The row moved into its own partial, **`components/_geo_signpost`**, and BOTH
204
+ engine chromes render it — the dropdown as before, and
205
+ `components/_link_sidebar` under its own admin-chipped `Geo` heading. Nothing
206
+ to configure: a host on either chrome gets the signage from the version bump.
207
+
208
+ **Exactly one of them, never both.** The sidebar carries the row only where it
209
+ IS the admin menu (the host declared an admin-flagged section, so the dropdown
210
+ is suppressed). A host declaring only public sections renders BOTH chromes by
211
+ design, and there the dropdown keeps the row — fixing a reach gap by showing
212
+ an admin the same row twice is not a fix.
213
+
214
+ **A forked chrome still needs one line**, because the engine cannot reach a
215
+ fork by rendering. Inside your own admin block:
216
+
217
+ ```erb
218
+ <%= render "components/geo_signpost",
219
+ variant: :sidebar, close_action: "$store.sidebars.gearOpen = false" %>
220
+ ```
221
+
222
+ `variant:` picks the chrome's row shape (`:dropdown`, the default, or
223
+ `:sidebar`); `close_action:` is the Alpine expression that closes YOUR panel
224
+ behind the row — the store flag belongs to the caller, so the partial never
225
+ hardcodes one. See [`docs/GEO.md`](docs/GEO.md#finding-the-page--the-signpost-in-whichever-chrome-the-app-has).
226
+
227
+ Behavior is otherwise unchanged: same three states, same words, same
228
+ self-gating on `admin?`, and `ENABLE_GEO_BLOCKING` still governs the LINK and
229
+ never the gate. One test hook was renamed — `data-test="geo-signpost-disabled"`,
230
+ was `admin-dropdown-geo-disabled` — since the row renders in two chromes now
231
+ and a name claiming one of them was a lie. Grepped ecosystem-wide first: it
232
+ appeared in no consumer.
233
+
191
234
  - **`.conic-surface` keeps its wash inside its own box.** The wash was an
192
235
  OVERSIZED pseudo (`inset: -50%`) spun by a `transform` keyframe, cut back to
193
236
  the element by the host's `overflow: hidden`. That clip is not dependable: the
@@ -13,29 +13,10 @@
13
13
  <div x-show="open" x-cloak @click.outside="open = false" x-transition:enter="transition ease-out duration-100" x-transition:enter-start="opacity-0 scale-95" x-transition:enter-end="opacity-100 scale-100" x-transition:leave="transition ease-in duration-75" x-transition:leave-start="opacity-100 scale-100" x-transition:leave-end="opacity-0 scale-95" class="absolute right-0 mt-2 w-44 bg-surface border border-subtle rounded-lg shadow-lg z-50 py-1">
14
14
  <a href="/admin/theme" class="block px-4 py-2 text-sm text-body hover:text-heading hover:bg-surface-alt transition">Theme</a>
15
15
  <a href="/admin/navbar" class="block px-4 py-2 text-sm text-body hover:text-heading hover:bg-surface-alt transition">Navbar</a>
16
- <%# GEO — shown in every app, LIVE only where it can actually be used.
17
- A disabled row rather than a hidden one is the whole point: an app that has
18
- not turned geo on still learns the feature exists and what to set, instead
19
- of it being invisible until someone reads the gem's docs.
20
-
21
- Two prerequisites, and the row says WHICH is missing, because they are
22
- fixed in different places — an env var on the dyno, a line in the app's
23
- initializer. The route check is not decoration: where draw_geo_routes is
24
- false, `admin_geo_path` does not exist and calling it RAISES, so the
25
- disabled branch must never reach for the helper. %>
26
- <% geo_routes = respond_to?(:admin_geo_path) %>
27
- <% if Studio.geo_blocking_enabled? && geo_routes %>
28
- <a href="<%= admin_geo_path %>" class="block px-4 py-2 text-sm text-body hover:text-heading hover:bg-surface-alt transition">Geo</a>
29
- <% else %>
30
- <span class="block px-4 py-2 text-sm text-muted cursor-not-allowed select-none"
31
- data-test="admin-dropdown-geo-disabled"
32
- title="<%= geo_routes ? "Set ENABLE_GEO_BLOCKING=true to manage geo blocking" : "Set config.draw_geo_routes = true in config/initializers/studio.rb, then ENABLE_GEO_BLOCKING=true" %>">
33
- Geo
34
- <span class="block text-xs text-muted">
35
- <%= geo_routes ? "Set ENABLE_GEO_BLOCKING=true" : "Draw the geo routes first" %>
36
- </span>
37
- </span>
38
- <% end %>
16
+ <%# GEO — the signpost lives in components/_geo_signpost, because this
17
+ dropdown is only ONE of the engine's two admin chromes and an app on the
18
+ other one never renders this file. Same row, same policy, both chromes. %>
19
+ <%= render "components/geo_signpost" %>
39
20
  <a href="/error_logs" class="block px-4 py-2 text-sm text-body hover:text-heading hover:bg-surface-alt transition">Error Logs</a>
40
21
  </div>
41
22
  </div>
@@ -0,0 +1,92 @@
1
+ <%# THE GEO SIGNPOST — one row, rendered into whichever admin chrome the app has.
2
+
3
+ WHY IT IS ITS OWN PARTIAL, and not a block inside the dropdown. 0.58.0 put
4
+ this row in components/_admin_dropdown on the premise that the shared
5
+ dropdown reaches every app "from one change". It does not. Counted against
6
+ the apps that actually consume this engine, the row reached NONE of them:
7
+
8
+ * an app whose admin menu is the LINK SIDEBAR never renders the dropdown —
9
+ studio_sidebar_replaces_admin_menu? suppresses it so the two cog glyphs
10
+ do not read as a double gear. Both mcritchie-studio and
11
+ mcritchie-industries declare an admin-flagged section, so both are that;
12
+ * an app that forks the navbar outright renders neither — turf-monster
13
+ forks _user_nav and _navbar and owns components/_gear_sidebar;
14
+ * nobody was left on the plain engine navbar to see it.
15
+
16
+ So the policy below now has ONE home, and BOTH engine-owned chromes render
17
+ it: components/_admin_dropdown and components/_link_sidebar. A host that
18
+ forks its chrome adopts it in one line, inside its own admin block — an ERB
19
+ render tag around:
20
+
21
+ render "components/geo_signpost",
22
+ variant: :sidebar, close_action: "$store.sidebars.gearOpen = false"
23
+
24
+ Locals:
25
+ variant — :dropdown (default), the gear dropdown's narrow rows;
26
+ :sidebar, the link sidebar's wide emoji rows.
27
+ close_action — optional Alpine expression run on click, so the panel the
28
+ row lives in closes behind it. The flag is the CALLER's
29
+ (linkTreeOpen here, gearOpen in a forked chrome), which is
30
+ exactly why this partial must not hardcode one.
31
+
32
+ ADMIN CHROME, self-gated. The gate lives HERE rather than in each caller so
33
+ no caller can forget it: the dropdown shipped without one once and offered
34
+ /admin/theme and /error_logs to signed-out strangers on a public page.
35
+ %>
36
+ <% if respond_to?(:admin?) && admin? %>
37
+ <%# The two prerequisites, resolved ONCE and shared by both variants — the
38
+ point of a single partial is that the two chromes cannot drift into
39
+ disagreeing about whether geo is reachable.
40
+
41
+ The route check is not decoration: where draw_geo_routes is false,
42
+ `admin_geo_path` is not defined at all, so the disabled branch must never
43
+ reach for it. A NoMethodError here takes down admin chrome on EVERY page.
44
+
45
+ They are named separately because they are FIXED in different places — an
46
+ env var on the dyno, a line in the app's initializer — so the row says
47
+ which one is missing rather than a vague "unavailable". %>
48
+ <% geo_routes = respond_to?(:admin_geo_path) %>
49
+ <% geo_live = Studio.geo_blocking_enabled? && geo_routes %>
50
+ <% geo_hint = geo_routes ? "Set ENABLE_GEO_BLOCKING=true" : "Draw the geo routes first" %>
51
+ <% geo_title = geo_routes ? "Set ENABLE_GEO_BLOCKING=true to manage geo blocking" : "Set config.draw_geo_routes = true in config/initializers/studio.rb, then ENABLE_GEO_BLOCKING=true" %>
52
+ <% geo_close = local_assigns[:close_action].presence %>
53
+
54
+ <% if local_assigns.fetch(:variant, :dropdown).to_sym == :sidebar %>
55
+ <%# Sidebar variant: the wide emoji row the link sidebar's own sections use,
56
+ so an engine-owned row is indistinguishable from a host-declared one. %>
57
+ <% if geo_live %>
58
+ <%= link_to admin_geo_path,
59
+ class: "group flex items-start gap-3 px-4 py-3 text-sm text-body hover:text-heading hover:bg-surface-alt focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary/40 focus-visible:ring-inset transition",
60
+ "@click": geo_close do %>
61
+ <span class="text-lg leading-none flex-shrink-0">🌍</span>
62
+ <span class="min-w-0">
63
+ <span class="block font-medium truncate">Geo</span>
64
+ <span class="block text-xs text-muted leading-snug mt-0.5">Country and subdivision blocking</span>
65
+ </span>
66
+ <% end %>
67
+ <% else %>
68
+ <span class="flex items-start gap-3 px-4 py-3 text-sm text-muted cursor-not-allowed select-none"
69
+ data-test="geo-signpost-disabled"
70
+ title="<%= geo_title %>">
71
+ <span class="text-lg leading-none flex-shrink-0">🌍</span>
72
+ <span class="min-w-0">
73
+ <span class="block font-medium truncate">Geo</span>
74
+ <span class="block text-xs text-muted leading-snug mt-0.5"><%= geo_hint %></span>
75
+ </span>
76
+ </span>
77
+ <% end %>
78
+ <% else %>
79
+ <%# Dropdown variant: the narrow row that shipped in 0.58.0, unchanged apart
80
+ from the test hook, which no longer names one chrome. %>
81
+ <% if geo_live %>
82
+ <a href="<%= admin_geo_path %>" class="block px-4 py-2 text-sm text-body hover:text-heading hover:bg-surface-alt transition">Geo</a>
83
+ <% else %>
84
+ <span class="block px-4 py-2 text-sm text-muted cursor-not-allowed select-none"
85
+ data-test="geo-signpost-disabled"
86
+ title="<%= geo_title %>">
87
+ Geo
88
+ <span class="block text-xs text-muted"><%= geo_hint %></span>
89
+ </span>
90
+ <% end %>
91
+ <% end %>
92
+ <% end %>
@@ -136,6 +136,47 @@
136
136
  <% end %>
137
137
  <% end %>
138
138
  <% end %>
139
+
140
+ <%# THE GEO SIGNPOST — how a sidebar-chrome app gets geo signage at all.
141
+
142
+ This panel IS the admin menu wherever the host declares an admin-flagged
143
+ section (studio_sidebar_replaces_admin_menu?), and in exactly that case
144
+ the engine suppresses components/_admin_dropdown — so before this block
145
+ every app on this chrome was guaranteed NEVER to see the row the 0.58.0
146
+ signpost added, including the one carrying the largest admin surface in
147
+ the ecosystem. mcritchie-studio is that one:
148
+ app/views/layouts/application.html.erb renders this partial, and
149
+ app/helpers/link_tree_helper.rb stamps `admin: true` on every admin
150
+ section it declares. mcritchie-industries is the other.
151
+
152
+ Engine-owned rather than host-declared on purpose. Appending it to
153
+ Studio.sidebar_sections instead would make studio_sidebar? and
154
+ studio_sidebar_replaces_admin_menu? true for apps that declare NO
155
+ sections — raising a sidebar where none existed and taking Theme,
156
+ Navbar and Error Logs away from apps that rely on the dropdown.
157
+
158
+ EXACTLY ONE HOME PER APP, which is what the second half of the gate
159
+ buys. A host that declares only PUBLIC sections keeps the dropdown —
160
+ the engine navbar renders BOTH chromes for that shape, deliberately, so
161
+ its admins never lose Theme, Navbar and Error Logs. Signposting geo here
162
+ as well would show such an admin the same row twice, in two menus open
163
+ at the same time. So the row goes wherever the admin menu IS: the
164
+ dropdown for that shape, this panel for the shape that suppresses the
165
+ dropdown.
166
+
167
+ The predicate is `studio_sidebar_replaces_admin_menu?` (see
168
+ StudioSidebarHelper), computed here from the sections already in hand
169
+ rather than called: this partial renders in hosts and test rigs that
170
+ provide studio_sidebar_sections without the rest of the helper, and a
171
+ bare call would raise for them. The two must stay the same question —
172
+ if the helper's rule changes, this changes with it. %>
173
+ <% if sidebar_admin && sections.any? { |section| section[:admin] } %>
174
+ <div class="px-4 pt-4 pb-1 label-upper font-bold text-muted flex items-center gap-2">
175
+ <span>Geo</span>
176
+ <span class="rounded border border-primary/20 bg-primary/10 px-1.5 py-0.5 text-[10px] font-mono text-primary">ADMIN</span>
177
+ </div>
178
+ <%= render "components/geo_signpost", variant: :sidebar, close_action: close_action %>
179
+ <% end %>
139
180
  </nav>
140
181
  <% end %>
141
182
 
@@ -1,3 +1,3 @@
1
1
  module Studio
2
- VERSION = "0.58.1"
2
+ VERSION = "0.59.0"
3
3
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: studio-engine
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.58.1
4
+ version: 0.59.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Alex McRitchie
@@ -378,6 +378,7 @@ files:
378
378
  - app/views/components/_emoji_swap.html.erb
379
379
  - app/views/components/_empty_state.html.erb
380
380
  - app/views/components/_geo_badge.html.erb
381
+ - app/views/components/_geo_signpost.html.erb
381
382
  - app/views/components/_google_logo.html.erb
382
383
  - app/views/components/_input.html.erb
383
384
  - app/views/components/_json_debug.html.erb