studio-engine 0.132.0 → 0.133.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.
Files changed (36) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +77 -0
  3. data/README.md +52 -24
  4. data/app/assets/tailwind/studio_engine/engine.css +141 -0
  5. data/app/javascript/studio/application.js +1 -0
  6. data/app/javascript/studio/carousel.js +66 -1
  7. data/app/javascript/studio/controllers/carousel_controller.js +139 -8
  8. data/app/javascript/studio/controllers/page_header_controller.js +64 -1
  9. data/app/javascript/studio/controllers/style_section_nav_controller.js +26 -0
  10. data/app/javascript/studio/in_page_links.js +308 -0
  11. data/app/javascript/studio/page_header.js +17 -0
  12. data/app/views/studio/_carousel.html.erb +33 -5
  13. data/app/views/studio/_page_header.html.erb +64 -12
  14. data/app/views/studio/auth/_sign_in_body.html.erb +236 -0
  15. data/app/views/studio/auth/_sign_in_card.html.erb +50 -0
  16. data/app/views/studio/auth/_sign_in_modal.html.erb +36 -0
  17. data/app/views/studio/modals/auth/_resend_footer.html.erb +6 -0
  18. data/app/views/style/_glass.html.erb +4 -8
  19. data/app/views/style/_loaders.html.erb +11 -35
  20. data/app/views/style/_modals.html.erb +29 -39
  21. data/app/views/style/_scrims.html.erb +10 -20
  22. data/app/views/style/_site_footer.html.erb +10 -18
  23. data/app/views/style/_specimen.html.erb +13 -41
  24. data/app/views/style/_tasks.html.erb +4 -22
  25. data/app/views/style/_trick_label.html.erb +33 -0
  26. data/app/views/style/_tricks.html.erb +127 -207
  27. data/app/views/style/index.html.erb +2 -2
  28. data/app/views/style/modals/_wallet_connect.html.erb +2 -1
  29. data/config/studio_tricks.yml +470 -0
  30. data/lib/studio/page_header.rb +53 -0
  31. data/lib/studio/tricks.rb +110 -0
  32. data/lib/studio/version.rb +1 -1
  33. data/lib/studio.rb +6 -4
  34. metadata +9 -3
  35. data/app/views/style/modals/_ds_email_field.html.erb +0 -58
  36. data/app/views/style/modals/_ds_resend_footer.html.erb +0 -70
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.132.0
4
+ version: 0.133.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Alex McRitchie
@@ -543,6 +543,7 @@ files:
543
543
  - app/javascript/studio/host_fetchers.js
544
544
  - app/javascript/studio/identity_mini.js
545
545
  - app/javascript/studio/image_upload.js
546
+ - app/javascript/studio/in_page_links.js
546
547
  - app/javascript/studio/infinite_scroll.js
547
548
  - app/javascript/studio/knowledge_transcript.js
548
549
  - app/javascript/studio/lazy_controllers.js
@@ -671,6 +672,9 @@ files:
671
672
  - app/views/studio/agent_chat/_turn.html.erb
672
673
  - app/views/studio/agent_chat/_typing.html.erb
673
674
  - app/views/studio/agent_chat/_voice.html.erb
675
+ - app/views/studio/auth/_sign_in_body.html.erb
676
+ - app/views/studio/auth/_sign_in_card.html.erb
677
+ - app/views/studio/auth/_sign_in_modal.html.erb
674
678
  - app/views/studio/banners/_app_banner.html.erb
675
679
  - app/views/studio/banners/_button.html.erb
676
680
  - app/views/studio/banners/_dev_mode_button.html.erb
@@ -818,6 +822,7 @@ files:
818
822
  - app/views/style/_specimen.html.erb
819
823
  - app/views/style/_tasks.html.erb
820
824
  - app/views/style/_theme.html.erb
825
+ - app/views/style/_trick_label.html.erb
821
826
  - app/views/style/_tricks.html.erb
822
827
  - app/views/style/_type_ladder.html.erb
823
828
  - app/views/style/board/_demo_card.html.erb
@@ -826,9 +831,7 @@ files:
826
831
  - app/views/style/modals/_age_gate.html.erb
827
832
  - app/views/style/modals/_birthday.html.erb
828
833
  - app/views/style/modals/_ds_close_x.html.erb
829
- - app/views/style/modals/_ds_email_field.html.erb
830
834
  - app/views/style/modals/_ds_rail_row.html.erb
831
- - app/views/style/modals/_ds_resend_footer.html.erb
832
835
  - app/views/style/modals/_ds_stack_demo.html.erb
833
836
  - app/views/style/modals/_email_change_pending.html.erb
834
837
  - app/views/style/modals/_it_begins.html.erb
@@ -840,6 +843,7 @@ files:
840
843
  - app/views/user_mailer/magic_link.html.erb
841
844
  - app/views/user_mailer/magic_link.text.erb
842
845
  - config/importmap.rb
846
+ - config/studio_tricks.yml
843
847
  - db/migrate/20260614000000_create_studio_email_deliveries.rb
844
848
  - db/migrate/20260620000001_create_studio_links.rb
845
849
  - db/migrate/20260620000002_allow_null_image_cache_owner.rb
@@ -944,6 +948,7 @@ files:
944
948
  - lib/studio/node_tooling/playwright_base.js
945
949
  - lib/studio/node_tooling/versions.json
946
950
  - lib/studio/oauth_identity.rb
951
+ - lib/studio/page_header.rb
947
952
  - lib/studio/partial_block.rb
948
953
  - lib/studio/profile_image.rb
949
954
  - lib/studio/profile_sections.rb
@@ -963,6 +968,7 @@ files:
963
968
  - lib/studio/survey/export.rb
964
969
  - lib/studio/tailwind_entry.rb
965
970
  - lib/studio/theme_resolver.rb
971
+ - lib/studio/tricks.rb
966
972
  - lib/studio/type_scale.rb
967
973
  - lib/studio/ui_primitives.rb
968
974
  - lib/studio/username_generator.rb
@@ -1,58 +0,0 @@
1
- <%#
2
- Living style guide specimen — the modal family's email input,
3
- studio/modals/shared/_email_field.
4
-
5
- THIS IS A BLOCK DEMO, NOT A CARD. The engine does not own an auth MODAL — the
6
- sign-in card is each app's, mounted on that app's own host and reviewed on that
7
- app's own section of this guide. What the engine owns is the PIECES that card
8
- is built from, and this is the one every app's auth card renders. It had no
9
- specimen of its own until 2026-09-09 because style/modals/_auth, a mirror of
10
- turf-monster's card, happened to render it — so retiring the mirror is what
11
- made the gap visible.
12
-
13
- BOTH FACES ARE SHOWN because `validator` is a real fork, not a flag. Off, the
14
- partial is self-contained and the SERVER stays the only boundary; on, it wraps
15
- itself in x-data="emailValidator()" and paints a right-edge spinner / check / X
16
- that only works if the CONSUMER ships that factory. An app that passes
17
- validator: true without the factory gets an Alpine scope error and a dead
18
- indicator, so the two are drawn side by side to make the dependency legible.
19
- This page ships no emailValidator, which is why the right-hand field's
20
- indicator stays blank — that is the demo, not a defect.
21
-
22
- Single root: the outer <div> is the host's required root.
23
- %>
24
- <div class="relative">
25
- <%= render "studio/modals/blocks/close_x", modal_store: "dsModals" %>
26
-
27
- <div class="text-center pt-1 mb-4">
28
- <h3 class="text-heading font-bold text-lg leading-tight">Email field</h3>
29
- <p class="text-xs text-muted mt-1">studio/modals/shared/_email_field</p>
30
- </div>
31
-
32
- <div class="space-y-4" x-data="{ email: '' }">
33
- <div>
34
- <p class="text-2xs font-bold text-secondary uppercase tracking-wide mb-1.5">Plain</p>
35
- <%= render "studio/modals/shared/email_field",
36
- name: nil, x_model: "email", required: true %>
37
- <p class="text-2xs text-muted mt-1.5">
38
- No dependency beyond the theme. <code class="font-mono">x_model</code>
39
- resolves in the ENCLOSING scope, so the value lands on the host component
40
- rather than on the field.
41
- </p>
42
- </div>
43
-
44
- <div>
45
- <p class="text-2xs font-bold text-secondary uppercase tracking-wide mb-1.5">With validator</p>
46
- <%= render "studio/modals/shared/email_field",
47
- name: nil, x_model: "email", required: true, validator: true %>
48
- <p class="text-2xs text-muted mt-1.5">
49
- Adds <code class="font-mono">x-data="emailValidator()"</code> and the
50
- right-edge indicator. <strong>The factory is the consumer&rsquo;s to
51
- ship</strong> &mdash; this guide does not, so the indicator stays blank here.
52
- </p>
53
- </div>
54
- </div>
55
-
56
- <button type="button" @click="$store.dsModals.close()"
57
- class="btn btn-primary btn-lg w-full mt-5">Done</button>
58
- </div>
@@ -1,70 +0,0 @@
1
- <%#
2
- Living style guide specimen — the magic-link resend footer,
3
- studio/modals/auth/_resend_footer.
4
-
5
- A BLOCK DEMO, for the same reason as _ds_email_field: the engine owns this
6
- fragment, not the auth card that renders it. Both of the auth card's
7
- link-sent steps render it, and they are identical — which is the whole reason
8
- it was extracted.
9
-
10
- IT NEEDS A HOST, AND THAT IS THE POINT. The footer reads props.submitting,
11
- props.resendCooldown and props.resendError, and calls resendMagicLink(), all
12
- resolved from the ENCLOSING x-data — never from a store. So the x-data below
13
- is a stand-in for the auth card's own component, and it is the contract this
14
- specimen documents: drop this partial into a scope that supplies none of those
15
- and nothing errors, the link simply never enables and the cooldown never
16
- counts. The stub here drives a real 5s cooldown so all three states are
17
- reachable: idle, in-flight, counting down.
18
-
19
- THE COOLDOWN IS SHORTENED ON PURPOSE. Production counts 60s; a reviewer will
20
- not wait 60s to watch a number tick, and a specimen nobody watches documents
21
- nothing. The partial does not own the duration — the host does.
22
-
23
- Single root: the outer <div> is the host's required root.
24
- %>
25
- <div class="relative"
26
- x-data="{
27
- props: { submitting: null, resendCooldown: 0, resendError: '' },
28
- _timer: null,
29
- resendMagicLink() {
30
- if (this.props.submitting || this.props.resendCooldown > 0) return;
31
- this.props.resendError = '';
32
- this.props.submitting = 'magic-link';
33
- setTimeout(() => {
34
- this.props.submitting = null;
35
- this.props.resendCooldown = 5;
36
- this._timer = setInterval(() => {
37
- this.props.resendCooldown -= 1;
38
- if (this.props.resendCooldown <= 0) clearInterval(this._timer);
39
- }, 1000);
40
- }, 900);
41
- },
42
- fail() {
43
- this.props.submitting = null;
44
- this.props.resendCooldown = 0;
45
- this.props.resendError = 'That link could not be sent. Try again in a moment.';
46
- },
47
- // Alpine calls destroy() when the component leaves the DOM. Without it the
48
- // interval outlives the closed card and keeps decrementing a dead object.
49
- destroy() { clearInterval(this._timer); }
50
- }">
51
- <%= render "studio/modals/blocks/close_x", modal_store: "dsModals" %>
52
-
53
- <div class="text-center pt-1 mb-4">
54
- <div class="text-2xl leading-none mb-2">📬</div>
55
- <h3 class="text-heading font-bold text-lg leading-tight">Check your email</h3>
56
- <p class="text-xs text-body mt-1">
57
- The footer below is the shared piece &mdash; everything above it belongs to
58
- the app&rsquo;s own card.
59
- </p>
60
- </div>
61
-
62
- <div class="border-t border-subtle pt-4">
63
- <%= render "studio/modals/auth/resend_footer", modal_store: "dsModals" %>
64
- </div>
65
-
66
- <button type="button" @click="fail()"
67
- class="block mx-auto mt-4 text-2xs text-muted hover:text-secondary underline underline-offset-2">
68
- Show the error line
69
- </button>
70
- </div>