studio-engine 0.114.1.rc1 → 0.114.2.rc1

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: b3cff8f80942f244b718fc77cc30b887f2b2e7ebc7442ed37f9654ef6868759e
4
- data.tar.gz: 0773c555963a091e9dc6b0a7379d196a90b1bc2ea64e8aafde5200e99de14e45
3
+ metadata.gz: d8f4151d1230d0b856b6960e64b6cb4d1c95a6a84c7a699b564c1517cf79342b
4
+ data.tar.gz: 3b558ba4c1c1338ed7079de81b4ed014f8b2ca77f340b2c70916aaa86b1742ba
5
5
  SHA512:
6
- metadata.gz: 1a3eb7cb288a08e0def13629d0ba7bf38c5bfd4a480497f8077cb0be549844b80ce215eee870eae180abdb95811cb01123bd32b33ff4894a6e20ace30df0dedb
7
- data.tar.gz: 3fc4b9eef2fbf8239d990ae88fa30599f7a0d85543e8b9642f9702dcdaa3cd4a2ab6209a8d66442bf959ea75a614636fbbb5e1f75b3bae0eacb7cc7dcbe9e14d
6
+ metadata.gz: f5d15fd2275e519d42ba93342499ddb304a3383a7e8029f7aa611826b5d8e7a542924cba6f863f48db6ae8784f8b3c40706742c21a593c05a731239d15661b3a
7
+ data.tar.gz: 03eef31def9eb2e19ec1e7a7b3c18f5c0e36b85efcab9544674609533265c5e2caf580d12c26d6a09d5dd07bb91046ab597af6afe8f64a476a09005c4a9f2999
data/CHANGELOG.md CHANGED
@@ -4,6 +4,83 @@ The format is [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). This pro
4
4
 
5
5
  ## Unreleased
6
6
 
7
+ ## 0.114.2 — 2026-10-26
8
+
9
+ ### Changed
10
+
11
+ - **The Tailwind layer is CSS-first; a host's whole adoption is `@import
12
+ "../builds/tailwind/studio_engine";`.** That entry now imports the new
13
+ `host.css` (`Studio::TailwindEntry` rewrites it right after
14
+ tailwindcss-rails' `Tailwindcss::Engines.bundle` writes it), which brings
15
+ `engine.css`, `@source` lines for every engine directory that emits a class
16
+ (views, components, helpers, `app/javascript` and `app/assets/javascripts`,
17
+ the vendored third-party bundles excluded by name) and `engine-motion.css`.
18
+ `engine.css` imports `theme.css` (the design tokens as `@theme inline
19
+ reference`, and the class-based `dark:` variant) and otherwise stays the
20
+ narrow component layer: its `@source "../../../components"` line moved to
21
+ `host.css`, so a build importing `engine.css` by path (the hub's client kit)
22
+ compiles only what it names. A host needs no `@config`, no
23
+ content glob over the gem found by a bundler call from Node, no safelist,
24
+ and no motion rake task. The recipe for a host still on `@config`:
25
+ README, "Tailwind". Hosts adopt on their own cards. A host that only bumps
26
+ the gem still builds, with no class lost, but its CSS grows by the motion
27
+ layer (about 44 KB unminified; McRitchie Industries, Cyvasse and Moms App
28
+ get it for the first time) and the classes the new `@source` lines find.
29
+ - **Engine classes are spelled whole, so the scan finds them.** The modal
30
+ blocks' `icon_color:` and `color:` tones (`_card_header`, `_success_card`,
31
+ `_processing_card`) are literal maps rather than `bg-<%= tone %>/15`, which
32
+ compiled only in a host whose safelist or markup spelled the same name; an
33
+ unknown token now paints as primary. Their check icon's `success` and
34
+ `warning` tones use `text-success-ink` and `text-warning-ink`: the bare
35
+ `text-success` the old interpolation produced is deliberately uncompilable,
36
+ so the "green check" painted the inherited colour in every host. The style
37
+ page's button specimens are keyed by whole class. `admin_model_table`'s
38
+ `min_width:` is an inline `min-width` style instead of an arbitrary
39
+ `min-w-[Npx]` class. Visible: a table given any width but 940 (which a host
40
+ happened to compile) gets its minimum width for the first time, so on a phone
41
+ it scrolls sideways instead of squeezing.
42
+ - **A host's build now carries five engine classes it could miss before**
43
+ (`ring-primary`, `ring-red-500`, `ring-2`, `opacity-30`, `!visible`), written
44
+ only in the engine's helpers and scripts, which no host scanned. Visible: a
45
+ rejected drag on the board shows its red ring, and in a host that defines
46
+ `theme-transition` (the hub) a theme switch now animates.
47
+
48
+ ### Deprecated
49
+
50
+ - **`tailwind/studio.tailwind.config.js`** is a shim for one release, so a host
51
+ still on `@config` builds unchanged; `test/integration/tailwind_preset_parity_test.rb`
52
+ holds it to the same utilities as `theme.css`. It is removed once every host
53
+ has adopted. Until a host migrates it gets the motion layer twice (through
54
+ `host.css` and through its own generated `studio_engine_motion` import);
55
+ the duplicate is harmless, and step 3 of the recipe removes it.
56
+ **Removing the shim will break, at build time or at runtime, everything that
57
+ still reads it:** every host's `config/tailwind.config.js` (hub, Turf
58
+ Monster, McRitchie Industries, Cyvasse, Moms App and any newer app built from
59
+ the old `NEW_APP_SETUP.md`), the hub's production code
60
+ `app/services/client_apps/studio_kit/css.rb` (`ENGINE_CONFIG` and its own
61
+ `SAFELIST`, which build the client kit CSS at request time), and the tests
62
+ that read the preset's text (hub `test/views/status_colour_tokens_test.rb`,
63
+ Turf `test/views/error_text_contrast_test.rb`). Each moves to `theme.css` on
64
+ its host's adoption card first.
65
+
66
+ ### Tests
67
+
68
+ - `test/support/engine_tailwind_build.rb` builds the engine the CSS-first way
69
+ (no `@config`), with `motion:`, `sources:` and `with_variant` for controls.
70
+ - `tailwind_probe_build_test.rb`: the tokens compile from `@theme` (and the
71
+ bare `text-<role>` still does not), the motion layer arrives through
72
+ `host.css`, and dropping the views, components or JavaScript `@source`
73
+ line loses classes only that directory emits.
74
+ - `component_tailwind_source_test.rb`: every class the five emitting
75
+ directories name, read independently of Tailwind's scanner, is in a
76
+ host-shaped build with no safelist; every emitting directory has an
77
+ `@source` line; exclusions name only vendored files.
78
+ - `test/views/dynamic_tailwind_class_guard_test.rb` refuses a Tailwind class
79
+ assembled at render time.
80
+ - `test/integration/tailwind_entry_test.rb` runs the real
81
+ `Tailwindcss::Engines.bundle` and reads the entry: `host.css`, and in the
82
+ control without the wrap, `engine.css`.
83
+
7
84
  ## 0.114.1 — 2026-10-25
8
85
 
9
86
  ## 0.114.0 — 2026-10-25
data/README.md CHANGED
@@ -45,6 +45,91 @@ resolved.
45
45
  - **Booking**: a simple scheduler on Google Calendar appointment schedules, with or without the footer. With `config.booking_url`, `studio_booking_frame` embeds Google's booking page, `studio_booking_link` opens it in a popup, and `config.draw_booking_routes = true` draws `/schedule` (or name the app's own page with `config.booking_path`). `config.booking_crop` crops the frame to the slot picker at rest, from numbers `bin/booking-crop-measure` reads off the app's own schedule. See [`docs/BOOKING.md`](docs/BOOKING.md).
46
46
  - **Surveys**: define a survey in app code (`config/surveys/*.rb`, `Studio.define_survey`) with six question types — emoji scale, 1–5 rating, single and multiple choice, short and long text — and the engine serves it at `/surveys/:slug`: one question per screen, autosave, resume, keyboard shortcuts, and a no-JavaScript fallback. `config.on_survey_completed` and `config.survey_ref_resolver` let the app fire its own goals and attribute email arrivals. An admin panel at `/admin/surveys` breaks results down per question and exports CSV. Opt in with `config.draw_survey_routes = true` and `config.draw_admin_survey_routes = true`. See [`docs/SURVEYS.md`](docs/SURVEYS.md).
47
47
 
48
+ ## Tailwind
49
+
50
+ The engine's Tailwind 4 layer is CSS-first. A host's whole adoption is two
51
+ lines in `app/assets/tailwind/application.css`:
52
+
53
+ ```css
54
+ @import "tailwindcss";
55
+ @import "../builds/tailwind/studio_engine";
56
+ ```
57
+
58
+ tailwindcss-rails writes `app/assets/builds/tailwind/studio_engine.css` from
59
+ Ruby on every build and watch (`bin/rails tailwindcss:engines` by hand), as an
60
+ absolute `@import` of the gem's `engine.css`. The engine then points that entry
61
+ at `app/assets/tailwind/studio_engine/host.css` (`Studio::TailwindEntry` wraps
62
+ the contract's `bundle`), and that one import brings everything a host used to
63
+ wire by hand:
64
+
65
+ | What | Where it lives | What it replaced |
66
+ |------|----------------|------------------|
67
+ | The design tokens (surfaces, inks, the primary ladder, brand palettes, `text-2xs`/`text-3xs`, Montserrat) and the class-based `dark:` variant | `theme.css`, `@theme inline reference` | `tailwind/studio.tailwind.config.js` and the host's `@config` |
68
+ | The scan of every engine directory that emits a class (views, components, helpers, both JavaScript trees) | `host.css`, `@source` | the host's content glob over the gem's views, found with a bundler call from Node |
69
+ | Every engine class, with no safelist | the scan above; no class is assembled at render time | the host's primary-colour safelist (about 240 classes) |
70
+ | The motion, effect and leveling primitives (`.spinner`, `.hold-btn`, `.level-badge-*`, ...) | `engine-motion.css`, imported last by `host.css` | `lib/tasks/tailwindcss_engine_motion.rake` and its `studio_engine_motion` import |
71
+
72
+ `test/integration/tailwind_probe_build_test.rb` and
73
+ `test/integration/component_tailwind_source_test.rb` build the engine exactly as
74
+ a host does and hold all four; `test/integration/tailwind_entry_test.rb` runs
75
+ the real `Tailwindcss::Engines.bundle` and reads the entry it leaves.
76
+
77
+ `engine.css` on its own stays the narrow layer: the theme and the component
78
+ `@utility` definitions, with no `@source` and no motion. A build that imports it
79
+ by path, such as the hub's client kit (`ClientApps::StudioKit::Css`, held to
80
+ 100 KB), compiles only the classes it names.
81
+
82
+ ### Migrating a host from `@config`
83
+
84
+ Hosts adopt on their own cards (hub, Turf Monster, Rolio, chain-ops, Rantly,
85
+ McRitchie Industries, Cyvasse, Moms App). Until each one does, it keeps
86
+ building unchanged: `tailwind/studio.tailwind.config.js` stays one release as a
87
+ deprecated shim, and `test/integration/tailwind_preset_parity_test.rb` holds it
88
+ to the same utilities as `theme.css`. A host that has bumped the gem but not
89
+ yet migrated gets the motion layer twice (once through `host.css`, once
90
+ through its own generated import). The duplicate is harmless and goes with
91
+ step 3.
92
+
93
+ 1. **Bump** `studio-engine` to the release that carries this section.
94
+ 2. **`application.css`**: delete the `@config` line. Keep `@import
95
+ "tailwindcss";` and `@import "../builds/tailwind/studio_engine";` first.
96
+ 3. **Motion**: delete `@import "../builds/tailwind/studio_engine_motion";`,
97
+ `lib/tasks/tailwindcss_engine_motion.rake` and the generated
98
+ `app/assets/builds/tailwind/studio_engine_motion.css`.
99
+ 4. **`config/tailwind.config.js`**: delete it, moving only what is the host's
100
+ own into `application.css`:
101
+ - **`content:`** goes. Tailwind 4 detects the app's own sources from the
102
+ project root and skips what `.gitignore` names; the engine scans itself.
103
+ If CI or Heroku installs gems into `vendor/bundle` inside the app, exclude
104
+ only their Markdown: `@source not "../../../vendor/bundle/**/*.md";`.
105
+ Never exclude the whole directory: the gem lives under it there, and the
106
+ hub's build once lost every engine class that way. A file the default scan
107
+ misses (say `config/initializers/studio.rb` carrying navbar word classes)
108
+ takes its own `@source` line.
109
+ - **`safelist`** goes. The engine's own classes no longer need it. If a host
110
+ class is genuinely data-driven, spell its variants whole in source; as a
111
+ last resort, `@source inline()` takes brace expansion. The hub's whole
112
+ safelist is
113
+ `@source inline("{bg,text,border}-primary{,-{50,{100..900..100}}}{,/{5,10,20,30,40,50}}");`
114
+ (231 classes).
115
+ - **`theme` extensions** become `@theme inline { ... }` after the engine
116
+ import: a screen is `--breakpoint-pill-narrow: 530px;`, a palette is
117
+ `--color-warning-500: #FF7C47;`, and a text-only colour is
118
+ `--text-color-violet-ink: var(--color-violet-ink);` (the same name
119
+ overrides the engine's, as Turf's `text-primary` does with
120
+ `--text-color-primary: rgb(var(--color-primary-ink-rgb));`).
121
+ - **`darkMode: 'class'`** goes; `theme.css` declares it.
122
+ 5. **Verify** with `bin/rails tailwindcss:build`, then compare the class set of
123
+ `app/assets/builds/tailwind.css` before and after. It may only grow (the
124
+ engine now also scans its helpers and scripts). Spot-check one class from
125
+ each column above: `.text-heading`, `.btn-primary`, `.spinner`.
126
+
127
+ When every host has adopted, the shim and the parity test are deleted in one
128
+ engine change. Before that, every reader of the preset has to move, not only
129
+ the build: the hub's `app/services/client_apps/studio_kit/css.rb` reads it at
130
+ runtime to build the client kit CSS, and a few host tests read its text
131
+ (CHANGELOG, Deprecated).
132
+
48
133
  ## Configuration
49
134
 
50
135
  Each consuming app configures the engine in `config/initializers/studio.rb`:
@@ -503,8 +588,8 @@ Two things that will bite you:
503
588
  tick *after* the stack empties, so a bare `.id` throws on every close.
504
589
 
505
590
  The scoped host takes its animations from `engine-motion.css` rather than an
506
- inline copy, so a consumer bundling that layer gets the same spring as the shared
507
- host.
591
+ inline copy, so every host (the layer rides the engine's one Tailwind import)
592
+ gets the same spring as the shared host.
508
593
 
509
594
  It carries the store API below with three deliberate differences: there is no
510
595
  `advance()`; `swap()` replaces the top entry immediately rather than running the
@@ -971,14 +1056,12 @@ config.navbar_z_tier = true # the live header at z-[var(--z-nav)], not z-50
971
1056
  truncating span with the host's class and nothing else. `navbar_wordmark`
972
1057
  wins when both are set. Rules: `lib/studio/navbar_brand.rb`.
973
1058
  - **The word classes live in the host's initializer**
974
- (`config/initializers/studio.rb`), which a host's Tailwind scan does not
975
- read by default (Turf Monster's `content:` lists `app/views`,
976
- `app/helpers`, `app/javascript` and the engine's views). A utility named
977
- only there, `text-heading` say, compiles only if some view also uses it.
978
- Add the initializer to the scan (`content:`, or `@source` under Tailwind 4),
979
- or give the word a class the host's CSS defines (`tm-wordmark`). The same
980
- rule as the engine's views glob:
981
- [NEW_APP_SETUP.md, section 14](docs/NEW_APP_SETUP.md#14-css-component-classes).
1059
+ (`config/initializers/studio.rb`), which a host's Tailwind scan may not
1060
+ read (a host still on `@config` lists only its `content:` globs). A utility
1061
+ named only there, `text-heading` say, compiles only if some view also uses
1062
+ it. Add the initializer to the scan (`@source "../../../config/initializers/studio.rb";`),
1063
+ or give the word a class the host's CSS defines (`tm-wordmark`). See
1064
+ [Tailwind](#tailwind).
982
1065
  - `navbar_root_hook` and `navbar_z_tier` are booleans, `false` by default.
983
1066
 
984
1067
  ### The account row
@@ -36,30 +36,19 @@
36
36
  .seeds-bar-continuous 5-segment lockstep progress fill (ported, TM)
37
37
 
38
38
  ─────────────────────────────────────────────────────────────────────────────
39
- OPT-IN — this layer does NOT auto-bundle.
40
-
41
- tailwindcss-rails' `tailwindcss:engines` task (Tailwindcss::Engines.bundle)
42
- generates a `builds/tailwind/<engine>.css` entry ONLY for the file named
43
- exactly `engine.css`. This file is `engine-motion.css`, so that matcher skips
44
- it BY CONSTRUCTION — it never rides the auto-generated `studio_engine.css`
45
- entry that `engine.css` uses, and consumers must adopt it deliberately.
46
-
47
- Adopt with ONE line in the consuming app's app/assets/tailwind/application.css,
48
- AFTER `@import "tailwindcss";` (and after your `../builds/tailwind/studio_engine`
49
- import so the theme role vars are in scope):
50
-
51
- @import "STUDIO_ENGINE_PATH/app/assets/tailwind/studio_engine/engine-motion.css";
52
-
53
- Resolve STUDIO_ENGINE_PATH the same way config/tailwind.config.js already
54
- resolves the shared preset — `bundle show studio-engine`. A one-time helper the
55
- app-side de-fork task can wire (mirrors the engines contract):
56
-
57
- # config/tailwind.config.js already computes this:
58
- # const studioPath = execSync('bundle show studio-engine').toString().trim()
59
- # emit an absolute @import into your app CSS (or a tiny generated entry).
60
-
61
- App-side wiring + removing the forked local copies lands in the follow-up
62
- adoption task; until then the MS/TM local copies stay canonical.
39
+ HOW IT REACHES A HOST — through host.css, which imports this file last.
40
+
41
+ A host's one line, `@import "../builds/tailwind/studio_engine";`, resolves to
42
+ host.css (Studio::TailwindEntry) and so brings this layer with the rest of
43
+ the engine. engine.css itself does not import it. It used to be OPT-IN: tailwindcss-rails'
44
+ engines contract (Tailwindcss::Engines.bundle) writes a builds entry only for
45
+ the file named exactly `engine.css`, so each host carried a rake task
46
+ (lib/tasks/tailwindcss_engine_motion.rake) that generated a second entry,
47
+ `builds/tailwind/studio_engine_motion.css`, plus a matching @import. Both are
48
+ retired: a host deletes the task and the import (README, "Tailwind"). A host
49
+ that keeps them for a release gets this layer twice, which is harmless —
50
+ every rule below is a plain class, keyframe or @property, so the duplicate
51
+ resolves to the same values.
63
52
 
64
53
  ─────────────────────────────────────────────────────────────────────────────
65
54
  DESIGN RULES
@@ -11,12 +11,17 @@
11
11
  (or manually: `bin/rails tailwindcss:engines`) because this file lives at the
12
12
  Rails-engines contract path: app/assets/tailwind/<engine_name>/engine.css.
13
13
 
14
+ That line resolves to host.css, not to this file: studio-engine writes the
15
+ builds entry itself (Studio::TailwindEntry) so it can carry, beside this
16
+ component layer, the @source scan of the engine's own markup and the motion
17
+ layer. See host.css. This file stays the NARROW layer: the theme (theme.css,
18
+ imported below, emits nothing on its own) and the component @utility
19
+ definitions, with no sources and no motion, so a build that imports it by
20
+ path (the hub's client kit) compiles only the classes it names.
21
+
14
22
  Requirements:
15
23
  - Tailwind v4 (this file uses @utility, so classes sort/override correctly
16
24
  in the utilities layer and take variants like `md:card`).
17
- - The consumer's tailwind config spreads the engine preset
18
- (tailwind/studio.tailwind.config.js) — it defines the surface / heading /
19
- subtle / primary token utilities @apply'd below.
20
25
 
21
26
  Colors resolve through the 7-role theme CSS custom properties (--color-cta,
22
27
  --color-surface, ...) emitted by studio_theme_css_tag, so themed apps restyle
@@ -28,13 +33,7 @@
28
33
  their local copies for this import.
29
34
  */
30
35
 
31
- /* -- Components: in every host's class scan --------------------------------
32
- The engine's ViewComponent classes (app/components) carry their colour
33
- classes in Ruby, and a host's Tailwind config scans only the engine's
34
- app/views. This adds app/components to the scan of every host that imports
35
- this file, with no host edit. The path is relative to this file.
36
- test/integration/component_tailwind_source_test.rb proves it compiles. */
37
- @source "../../../components";
36
+ @import "./theme.css";
38
37
 
39
38
  /* -- Layer scale ------------------------------------------------------------
40
39
  The ONE stacking order every Studio app shares. Read the ORDER, not the
@@ -0,0 +1,56 @@
1
+ /*
2
+ studio-engine HOST ENTRY — what a host's one Tailwind line resolves to.
3
+
4
+ @import "tailwindcss";
5
+ @import "../builds/tailwind/studio_engine";
6
+
7
+ tailwindcss-rails' engines contract (Tailwindcss::Engines.bundle) writes
8
+ app/assets/builds/tailwind/studio_engine.css as an @import of the gem's
9
+ engine.css. Studio::TailwindEntry (lib/studio/tailwind_entry.rb) rewrites
10
+ that entry, right after the bundle runs, to import THIS file instead, so a
11
+ host gets, from the same line and with nothing of its own:
12
+
13
+ - engine.css: the theme (theme.css, the tokens that replaced the JS
14
+ preset and the host's `@config`) and the component @utility layer;
15
+ - the @source scan below, so no host lists the gem in a content glob or
16
+ safelists its classes;
17
+ - engine-motion.css, the motion / effect / leveling layer, which used to
18
+ need a per-host rake task (lib/tasks/tailwindcss_engine_motion.rake).
19
+
20
+ The path is resolved by Ruby from the gem's root, so Node never runs bundler
21
+ to find the gem. engine.css stays importable on its own as the narrow layer.
22
+ Recipe: README, "Tailwind".
23
+ */
24
+ @import "./engine.css";
25
+
26
+ /* -- Sources: every engine directory that emits a class --------------------
27
+ Tailwind v4 compiles only the classes it SEES, so every directory whose
28
+ files put a utility class on the page is scanned here, relative to this
29
+ file, in every host that imports it, with no host edit: views and
30
+ partials, the ViewComponents (their colour classes live in Ruby), the
31
+ helpers that build markup (admin_model_table), and both JavaScript trees
32
+ (classes added at runtime).
33
+
34
+ The exclusions are NARROW on purpose: the vendored, minified third-party
35
+ bundles (Stimulus, Alpine, Leaflet, Sortable, canvas-confetti) are not
36
+ engine markup, and scanning them mints junk utilities from their
37
+ identifiers. Never exclude a whole directory: the hub's
38
+ `@source not "../../../vendor/bundle"` once dropped every one of these
39
+ lines in CI, because the gem itself lives under vendor/bundle there.
40
+
41
+ test/integration/component_tailwind_source_test.rb proves every class these
42
+ directories emit compiles, and that dropping any one line turns it red. A
43
+ class built by interpolation (`bg-<%= tone %>`) is invisible to the scan;
44
+ test/views/dynamic_tailwind_class_guard_test.rb refuses one. */
45
+ @source "../../../views";
46
+ @source "../../../components";
47
+ @source "../../../helpers";
48
+ @source "../../../javascript";
49
+ @source not "../../../javascript/studio/vendor";
50
+ @source "../../javascripts";
51
+ @source not "../../javascripts/studio/{alpine,canvas_confetti,leaflet,sortable}.js";
52
+
53
+ /* -- Motion / effect / leveling layer ---------------------------------------
54
+ Last, so the theme and component layer it reads are in scope, as when hosts
55
+ imported it after engine.css themselves. */
56
+ @import "./engine-motion.css";
@@ -0,0 +1,139 @@
1
+ /*
2
+ studio-engine THEME — the shared Tailwind v4 design tokens, CSS-first.
3
+
4
+ This file replaced tailwind/studio.tailwind.config.js (the JS preset every
5
+ consumer spread through `@config`, after shelling out to bundler from Node to
6
+ find the gem). engine.css imports it (and host.css imports engine.css), so a
7
+ consumer gets these tokens from the same one line that brings the component
8
+ layer:
9
+
10
+ @import "tailwindcss";
11
+ @import "../builds/tailwind/studio_engine";
12
+
13
+ No `@config`, no bundler call from Node, no generated motion entry.
14
+
15
+ WHY `@theme inline`. Every runtime token here (surface, heading, the primary
16
+ ladder, the status inks) is a CSS custom property that studio_theme_css_tag
17
+ writes per app and per theme. A plain `@theme` would ALSO declare
18
+ `--color-surface: var(--color-surface)` on :root — a self-reference, which is
19
+ invalid — and would put Tailwind's copy in a cascade race with the theme tag.
20
+ `inline` makes each utility carry the value itself (`bg-surface` compiles to
21
+ `background-color: var(--color-surface)`, exactly what the JS preset
22
+ compiled to) and declares nothing on :root.
23
+
24
+ WHY THE PER-UTILITY NAMESPACES. The status roles are deliberately NOT plain
25
+ colours. A bare role colour as text (text-danger, text-warning, text-success)
26
+ fails WCAG AA on one theme or the other, so it must stay uncompilable; the
27
+ text inks (*-ink, derived per theme by ThemeResolver#contrast_ink) are the
28
+ text spelling. Tailwind v4 reads `--text-color-*`, `--background-color-*` and
29
+ `--border-color-*` for ONLY that utility family, so registering a fill there
30
+ cannot mint the text utility, the same split the preset's textColor /
31
+ backgroundColor / borderColor keys made.
32
+ */
33
+
34
+ /* darkMode: 'class' — the theme tag toggles .dark on <html>. */
35
+ @custom-variant dark (&:is(.dark *));
36
+
37
+ @theme inline reference {
38
+ /* fontFamily — REPLACED, not extended (the preset set theme.fontFamily). */
39
+ --font-*: initial;
40
+ --font-sans: Montserrat, system-ui, sans-serif;
41
+ --font-mono: ui-monospace, SFMono-Regular, monospace;
42
+
43
+ /* Sub-xs type scale for dense UI (badges, meta rows, tick labels). Bare
44
+ sizes, no line-height, so text-2xs / text-3xs are drop-in replacements for
45
+ the text-[11px] / text-[10px] arbitrary values. */
46
+ --text-2xs: 0.6875rem; /* 11px */
47
+ --text-3xs: 0.625rem; /* 10px */
48
+
49
+ /* Theme-aware semantic surfaces (studio_theme_css_tag). */
50
+ --color-page: var(--color-page);
51
+ --color-surface: var(--color-surface);
52
+ --color-surface-alt: var(--color-surface-alt);
53
+ --color-inset: var(--color-inset);
54
+
55
+ /* The dynamic primary palette, from the theme's primary role. The -rgb
56
+ triples are space lists, so `rgb(var(--x-rgb))` is valid and Tailwind's
57
+ opacity modifier (bg-primary/20) mixes it. */
58
+ --color-primary: rgb(var(--color-primary-rgb));
59
+ --color-primary-50: rgb(var(--color-primary-50-rgb));
60
+ --color-primary-100: rgb(var(--color-primary-100-rgb));
61
+ --color-primary-200: rgb(var(--color-primary-200-rgb));
62
+ --color-primary-300: rgb(var(--color-primary-300-rgb));
63
+ --color-primary-400: rgb(var(--color-primary-400-rgb));
64
+ --color-primary-500: rgb(var(--color-primary-500-rgb));
65
+ --color-primary-600: rgb(var(--color-primary-600-rgb));
66
+ --color-primary-700: rgb(var(--color-primary-700-rgb));
67
+ --color-primary-800: rgb(var(--color-primary-800-rgb));
68
+ --color-primary-900: rgb(var(--color-primary-900-rgb));
69
+
70
+ /* Brand palettes. */
71
+ --color-mint: #06D6A0;
72
+ --color-mint-50: #e6faf4;
73
+ --color-mint-100: #b3f0de;
74
+ --color-mint-200: #80e6c8;
75
+ --color-mint-300: #4ddcb2;
76
+ --color-mint-400: #1ad29c;
77
+ --color-mint-500: #06D6A0;
78
+ --color-mint-600: #05b888;
79
+ --color-mint-700: #049a70;
80
+ --color-mint-800: #037c58;
81
+ --color-mint-900: #025e40;
82
+
83
+ --color-navy: #1A1535;
84
+ --color-navy-50: #e8e7ed;
85
+ --color-navy-100: #b8b5c8;
86
+ --color-navy-200: #8883a3;
87
+ --color-navy-300: #58517e;
88
+ --color-navy-400: #3a3359;
89
+ --color-navy-500: #1A1535;
90
+ --color-navy-600: #16122e;
91
+ --color-navy-700: #120f27;
92
+ --color-navy-800: #0e0c20;
93
+ --color-navy-900: #0a0919;
94
+
95
+ --color-violet: #8E82FE;
96
+ --color-violet-50: #f0eeff;
97
+ --color-violet-100: #EAE8FF;
98
+ --color-violet-200: #b2aafe;
99
+ --color-violet-300: #C5C0FE;
100
+ --color-violet-400: #8E82FE;
101
+ --color-violet-500: #8E82FE;
102
+ --color-violet-600: #6558e5;
103
+ --color-violet-700: #6558E0;
104
+ --color-violet-800: #3b2cb3;
105
+ --color-violet-900: #3D2FB5;
106
+
107
+ --color-mist: #F7F6FF;
108
+ --color-lavender: #E8E6F0;
109
+ --color-slate: #6B6580;
110
+ --color-charcoal: #2D2648;
111
+ --color-midnight: #120F28;
112
+ --color-ember: #FF8C69;
113
+ --color-gold: #FFD166;
114
+ --color-magenta: #F72585;
115
+
116
+ /* Text-only tokens. */
117
+ --text-color-heading: var(--color-text);
118
+ --text-color-body: var(--color-text-body);
119
+ --text-color-secondary: var(--color-text-secondary);
120
+ --text-color-muted: var(--color-text-muted);
121
+ /* The status roles as TEXT, held to AA on every surface. There is
122
+ deliberately NO text-danger, text-warning or text-success. */
123
+ --text-color-danger-ink: var(--color-danger-ink);
124
+ --text-color-warning-ink: var(--color-warning-ink);
125
+ --text-color-success-ink: var(--color-success-ink);
126
+ /* The primary role as chip text; text-primary stays the brand colour. */
127
+ --text-color-primary-ink: var(--color-primary-ink);
128
+
129
+ /* The status-role FILLS, for backgrounds and borders ONLY. */
130
+ --background-color-success: var(--color-success);
131
+ --background-color-warning: var(--color-warning);
132
+ --background-color-danger: var(--color-danger);
133
+
134
+ --border-color-subtle: var(--color-border);
135
+ --border-color-strong: var(--color-border-strong);
136
+ --border-color-success: var(--color-success);
137
+ --border-color-warning: var(--color-warning);
138
+ --border-color-danger: var(--color-danger);
139
+ }
@@ -12,6 +12,11 @@ module Studio
12
12
  # <td class="px-4 py-3"><%= time_ago_in_words(user.created_at) %> ago</td>
13
13
  # <% end %>
14
14
  #
15
+ # min_width is an inline style, not an arbitrary min-width class: Tailwind compiles
16
+ # only class names it can read in source, and a width interpolated at render
17
+ # time is never in source, so the class painted only in a host whose own
18
+ # markup happened to spell the same width.
19
+ #
15
20
  # The block yields each record and returns that row's <td> cells. Output matches
16
21
  # the long-standing markup (id="models-<key>-table", overflow wrapper, hover
17
22
  # rows, colspan empty state) so existing view assertions keep passing.
@@ -34,7 +39,7 @@ module Studio
34
39
  table = content_tag(:table,
35
40
  content_tag(:thead, header_row, class: "bg-surface-alt text-muted text-xs uppercase tracking-wide") +
36
41
  content_tag(:tbody, body, class: "divide-y divide-subtle"),
37
- id: "models-#{key}-table", class: "w-full min-w-[#{min_width}px] text-sm")
42
+ id: "models-#{key}-table", class: "w-full text-sm", style: "min-width: #{min_width.to_i}px")
38
43
 
39
44
  content_tag(:div, table, class: "overflow-x-auto")
40
45
  end
@@ -30,8 +30,8 @@
30
30
  Animations come from the shared motion layer
31
31
  (app/assets/tailwind/studio_engine/engine-motion.css) — the .modal-card-* and
32
32
  .modal-backdrop-* classes — rather than an inline copy, so this stays small.
33
- A consumer that does not bundle engine-motion.css still gets a working modal,
34
- just without the spring.
33
+ every host's engine import brings that layer (host.css); a page built without it
34
+ still gets a working modal, just without the spring.
35
35
 
36
36
  Locals:
37
37
  store — REQUIRED. Alpine store name, e.g. "emailModals". Must be unique
@@ -34,6 +34,20 @@
34
34
  %>
35
35
  <%
36
36
  icon_color = local_assigns[:icon_color] || 'primary'
37
+ # Each tone is spelled out WHOLE, never assembled from "bg-" + icon_color:
38
+ # Tailwind compiles only the class names it can read in this file, so an
39
+ # assembled one painted only in a host that happened to use or safelist it.
40
+ # The text half is the AA ink, not the bare role colour, which the theme
41
+ # leaves uncompilable on purpose (theme.css). An unknown token paints as
42
+ # primary. test/views/dynamic_tailwind_class_guard_test.rb refuses a
43
+ # rebuilt interpolation.
44
+ icon_tones = {
45
+ 'primary' => { pill: 'bg-primary/15', ink: 'text-primary' },
46
+ 'success' => { pill: 'bg-success/15', ink: 'text-success-ink' },
47
+ 'warning' => { pill: 'bg-warning/15', ink: 'text-warning-ink' },
48
+ 'mint' => { pill: 'bg-mint/15', ink: 'text-mint' }
49
+ }
50
+ icon_tone = icon_tones.fetch(icon_color.to_s) { icon_tones['primary'] }
37
51
  size = local_assigns[:size] || :md
38
52
  pill_class = size == :lg ? 'w-14 h-14' : 'w-12 h-12'
39
53
  svg_class = size == :lg ? 'w-7 h-7' : 'w-6 h-6'
@@ -56,8 +70,8 @@
56
70
  </svg>
57
71
  </div>
58
72
  <% elsif local_assigns[:icon] == :external_tab %>
59
- <div class="mx-auto <%= pill_class %> rounded-full bg-<%= icon_color %>/15 flex items-center justify-center mb-4">
60
- <svg class="<%= svg_class %> text-<%= icon_color %>" fill="none" stroke="currentColor" stroke-width="2.5" viewBox="0 0 24 24">
73
+ <div class="mx-auto <%= pill_class %> rounded-full <%= icon_tone[:pill] %> flex items-center justify-center mb-4">
74
+ <svg class="<%= svg_class %> <%= icon_tone[:ink] %>" fill="none" stroke="currentColor" stroke-width="2.5" viewBox="0 0 24 24">
61
75
  <path stroke-linecap="round" stroke-linejoin="round" d="M4.5 19.5l15-15m0 0H8.25m11.25 0v11.25"/>
62
76
  </svg>
63
77
  </div>
@@ -66,8 +80,8 @@
66
80
  <% elsif local_assigns[:icon_emoji] %>
67
81
  <div class="text-5xl mb-4 leading-none"><%= icon_emoji %></div>
68
82
  <% else %>
69
- <div class="mx-auto <%= pill_class %> rounded-full bg-<%= icon_color %>/15 flex items-center justify-center mb-4">
70
- <svg class="<%= svg_class %> text-<%= icon_color %>" fill="none" stroke="currentColor" stroke-width="3" viewBox="0 0 24 24">
83
+ <div class="mx-auto <%= pill_class %> rounded-full <%= icon_tone[:pill] %> flex items-center justify-center mb-4">
84
+ <svg class="<%= svg_class %> <%= icon_tone[:ink] %>" fill="none" stroke="currentColor" stroke-width="3" viewBox="0 0 24 24">
71
85
  <path stroke-linecap="round" stroke-linejoin="round" d="M5 13l4 4L19 7"/>
72
86
  </svg>
73
87
  </div>
@@ -37,6 +37,16 @@
37
37
  'lg' => 'w-16 h-16 border-4'
38
38
  }
39
39
  spinner_class = sizes[size] || sizes['md']
40
+ # Each tone is spelled out WHOLE, never assembled from "border-" + color:
41
+ # Tailwind compiles only the class names it can read in this file. An
42
+ # unknown token spins in primary.
43
+ # test/views/dynamic_tailwind_class_guard_test.rb refuses a rebuilt one.
44
+ tones = {
45
+ 'primary' => 'border-primary/30 border-t-primary',
46
+ 'success' => 'border-success/30 border-t-success',
47
+ 'warning' => 'border-warning/30 border-t-warning'
48
+ }
49
+ tone_class = tones.fetch(color.to_s) { tones['primary'] }
40
50
  # min_duration mirrors StudioModals.MIN_LOAD_MS (the JS default) so the Ruby and
41
51
  # JS sides of the convention agree on the same floor.
42
52
  min_duration = local_assigns[:min_duration] || 1400
@@ -62,7 +72,7 @@
62
72
  end
63
73
  %>
64
74
  <div class="text-center py-6"<%= resolve_attrs %>>
65
- <div class="mx-auto <%= spinner_class %> rounded-full border-<%= color %>/30 border-t-<%= color %> animate-spin mb-5"></div>
75
+ <div class="mx-auto <%= spinner_class %> rounded-full <%= tone_class %> animate-spin mb-5"></div>
66
76
  <% if local_assigns[:title_key] %>
67
77
  <p class="text-base font-bold text-heading mb-1" x-text="<%= title_key %>"></p>
68
78
  <% elsif local_assigns[:title] %>
@@ -80,6 +80,20 @@
80
80
  %>
81
81
  <%
82
82
  icon_color = local_assigns[:icon_color] || 'primary'
83
+ # Each tone is spelled out WHOLE, never assembled from "bg-" + icon_color:
84
+ # Tailwind compiles only the class names it can read in this file, so an
85
+ # assembled one painted only in a host that happened to use or safelist it.
86
+ # The text half is the AA ink, not the bare role colour, which the theme
87
+ # leaves uncompilable on purpose (theme.css). An unknown token paints as
88
+ # primary. test/views/dynamic_tailwind_class_guard_test.rb refuses a
89
+ # rebuilt interpolation.
90
+ icon_tones = {
91
+ 'primary' => { pill: 'bg-primary/15', ink: 'text-primary' },
92
+ 'success' => { pill: 'bg-success/15', ink: 'text-success-ink' },
93
+ 'warning' => { pill: 'bg-warning/15', ink: 'text-warning-ink' },
94
+ 'mint' => { pill: 'bg-mint/15', ink: 'text-mint' }
95
+ }
96
+ icon_tone = icon_tones.fetch(icon_color.to_s) { icon_tones['primary'] }
83
97
  redirect_secs = local_assigns[:auto_redirect_seconds] || 5
84
98
  has_redirect = !!local_assigns[:auto_redirect_url_key]
85
99
  fire_confetti = !!local_assigns[:confetti]
@@ -182,8 +196,8 @@
182
196
  <% if local_assigns[:icon_emoji] %>
183
197
  <div class="text-5xl mb-4 leading-none"><%= icon_emoji %></div>
184
198
  <% else %>
185
- <div class="mx-auto w-14 h-14 rounded-full bg-<%= icon_color %>/15 flex items-center justify-center mb-4">
186
- <svg class="w-7 h-7 text-<%= icon_color %>" fill="none" stroke="currentColor" stroke-width="3" viewBox="0 0 24 24">
199
+ <div class="mx-auto w-14 h-14 rounded-full <%= icon_tone[:pill] %> flex items-center justify-center mb-4">
200
+ <svg class="w-7 h-7 <%= icon_tone[:ink] %>" fill="none" stroke="currentColor" stroke-width="3" viewBox="0 0 24 24">
187
201
  <path stroke-linecap="round" stroke-linejoin="round" d="M5 13l4 4L19 7"/>
188
202
  </svg>
189
203
  </div>
@@ -28,8 +28,8 @@
28
28
  Free Entry Earned reward, gated by :leveling).
29
29
 
30
30
  The modal motion (modal-card-* / modal-backdrop-* keyframes + studio-modal-
31
- drain + body.modal-open) ships in engine-motion.css, so a consumer that
32
- bundles it animates these for real. %>
31
+ drain + body.modal-open) ships in engine-motion.css, which every host's
32
+ engine import brings (host.css), so every consumer animates these for real. %>
33
33
  <%
34
34
  web3_on = Studio.feature?(:web3)
35
35
  leveling_on = Studio.feature?(:leveling)
@@ -4,7 +4,8 @@
4
4
  rendered primitive, its class name (one-click copy), and a copyable usage
5
5
  snippet. Every class here is a REAL engine class (engine.css component layer
6
6
  or engine-motion.css motion/effect/leveling layer), so a consumer that
7
- bundles both styles the specimens for real.
7
+ imports the engine's Tailwind entry (host.css brings both) styles the
8
+ specimens for real.
8
9
 
9
10
  The Leveling group renders the REAL .level-badge tiers ported into the engine
10
11
  from Turf Monster, capability-gated by Studio.feature?(:leveling). When a host
@@ -15,15 +16,17 @@
15
16
  The color tokens/swatches now live in the Theme section (the landing section
16
17
  owns "the colors"); this section is the trick board. %>
17
18
  <%
19
+ # Keyed by the WHOLE class, never "btn-" + a name: Tailwind compiles only the
20
+ # @utility names it can read in source.
18
21
  button_variants = {
19
- "primary" => "Primary",
20
- "secondary" => "Secondary",
21
- "success" => "Success",
22
- "danger" => "Danger",
23
- "warning" => "Warning",
24
- "neutral" => "Neutral",
25
- "outline" => "Outline",
26
- "google" => "Continue with Google"
22
+ "btn-primary" => "Primary",
23
+ "btn-secondary" => "Secondary",
24
+ "btn-success" => "Success",
25
+ "btn-danger" => "Danger",
26
+ "btn-warning" => "Warning",
27
+ "btn-neutral" => "Neutral",
28
+ "btn-outline" => "Outline",
29
+ "btn-google" => "Continue with Google"
27
30
  }
28
31
 
29
32
  leveling_on = Studio.feature?(:leveling)
@@ -51,9 +54,9 @@
51
54
  <div class="grid gap-4 sm:grid-cols-2 lg:grid-cols-3">
52
55
  <% button_variants.each do |name, label| %>
53
56
  <%= render layout: "style/specimen",
54
- locals: { klass: ".btn .btn-#{name}",
55
- usage: %(<button class="btn btn-#{name}">#{label}</button>) } do %>
56
- <button type="button" class="btn btn-<%= name %>"><%= label %></button>
57
+ locals: { klass: ".btn .#{name}",
58
+ usage: %(<button class="btn #{name}">#{label}</button>) } do %>
59
+ <button type="button" class="btn <%= name %>"><%= label %></button>
57
60
  <% end %>
58
61
  <% end %>
59
62
 
@@ -21,8 +21,9 @@
21
21
  app/views/style/_host.html.erb.
22
22
 
23
23
  Specimens use ONLY the engine's own classes (engine.css component layer plus
24
- engine-motion.css motion/effect/leveling layer), so a consumer that bundles
25
- engine.css and imports engine-motion.css styles them for real. The primitive
24
+ engine-motion.css motion/effect/leveling layer), so a consumer that imports
25
+ the engine's Tailwind entry (host.css: engine.css and engine-motion.css)
26
+ styles them for real. The primitive
26
27
  CSS is NOT inlined here - the consumer's Tailwind build provides it. Page
27
28
  chrome uses the engine's own .card / .label-upper / theme tokens. %>
28
29
  <%
data/lib/studio/engine.rb CHANGED
@@ -330,6 +330,13 @@ module Studio
330
330
  # Studio.survey_definitions_path, loaded on boot and again on each
331
331
  # development reload so an edited definition takes effect without a restart.
332
332
  # Surveys defined in an initializer instead stay registered either way.
333
+ # The host's Tailwind entry for this engine imports host.css, not the bare
334
+ # engine.css (Studio::TailwindEntry says why). Rake's tailwindcss:engines
335
+ # runs after :environment, so every initializer has run by then.
336
+ initializer "studio.tailwind_entry", after: :load_config_initializers do
337
+ Studio::TailwindEntry.install!
338
+ end
339
+
333
340
  config.to_prepare do
334
341
  dir = Studio.survey_definitions_path
335
342
  Studio::Survey.load_definitions!(Rails.root.join(dir)) if dir.present? && Rails.root
@@ -0,0 +1,72 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "fileutils"
4
+ require "pathname"
5
+
6
+ module Studio
7
+ # Points a host's Tailwind entry for this engine at host.css.
8
+ #
9
+ # tailwindcss-rails' engines contract (Tailwindcss::Engines.bundle, run by
10
+ # `tailwindcss:engines`, a prerequisite of tailwindcss:build, :watch and so
11
+ # of assets:precompile) writes app/assets/builds/tailwind/studio_engine.css as
12
+ # an @import of the gem's app/assets/tailwind/studio_engine/engine.css. That
13
+ # file has to stay the NARROW component layer, because a build may import it
14
+ # by path and want only the classes it names (the hub's client kit, held to a
15
+ # 100 KB budget). So right after the bundle runs, this rewrites the entry to
16
+ # import host.css instead: engine.css plus the engine's @source scan and the
17
+ # motion layer. A host's `@import "../builds/tailwind/studio_engine";` then
18
+ # brings all three, with no `@config`, no safelist and no rake task of its
19
+ # own, and the gem's path is resolved here in Ruby, never by Node running
20
+ # bundler.
21
+ #
22
+ # Prepended rather than added as a rake action: a second action on
23
+ # `tailwindcss:engines` runs in task-load order, and one that ran BEFORE the
24
+ # bundle would be deleted by it (the bundle removes and rewrites the file).
25
+ # super-then-write is ordered by construction.
26
+ module TailwindEntry
27
+ HOST_CSS = "app/assets/tailwind/studio_engine/host.css"
28
+ ENTRY = "app/assets/builds/tailwind/studio_engine.css"
29
+
30
+ module_function
31
+
32
+ def entry_path(app_root) = Pathname(app_root).join(ENTRY)
33
+
34
+ def contents(engine_root)
35
+ <<~CSS
36
+ /* DO NOT MODIFY THIS FILE, it was auto-generated by studio-engine (Studio::TailwindEntry) */
37
+
38
+ @import "#{Pathname(engine_root).join(HOST_CSS)}";
39
+ CSS
40
+ end
41
+
42
+ # Writes the entry. Only when the bundle wrote one: an app whose engines
43
+ # contract found no engine.css gets nothing it did not ask for.
44
+ def write!(app_root:, engine_root:)
45
+ path = entry_path(app_root)
46
+ return false unless path.file?
47
+
48
+ File.write(path, contents(engine_root))
49
+ true
50
+ end
51
+
52
+ # Wraps Tailwindcss::Engines.bundle.
53
+ module Bundle
54
+ def bundle(...)
55
+ result = super
56
+ Studio::TailwindEntry.write!(app_root: Rails.root, engine_root: Studio::Engine.root)
57
+ result
58
+ end
59
+ end
60
+
61
+ # Idempotent. A host without tailwindcss-rails' engines contract is left
62
+ # alone: it never had the entry this rewrites.
63
+ def install!
64
+ require "tailwindcss/engines"
65
+ singleton = ::Tailwindcss::Engines.singleton_class
66
+ singleton.prepend(Bundle) unless singleton.ancestors.include?(Bundle)
67
+ true
68
+ rescue LoadError
69
+ false
70
+ end
71
+ end
72
+ end
@@ -1,3 +1,3 @@
1
1
  module Studio
2
- VERSION = "0.114.1.rc1"
2
+ VERSION = "0.114.2.rc1"
3
3
  end
data/lib/studio.rb CHANGED
@@ -21,6 +21,7 @@ require "studio/geo"
21
21
  require "studio/geo/lookup"
22
22
  require "studio/link_preview"
23
23
  require "studio/engine"
24
+ require "studio/tailwind_entry"
24
25
  require "studio/color_scale"
25
26
  require "studio/environment_banner"
26
27
  require "studio/theme_resolver"
@@ -1,3 +1,17 @@
1
+ // DEPRECATED — kept for ONE release so consumers can adopt on their own cards.
2
+ //
3
+ // The engine's Tailwind layer is CSS-first: the tokens below now live in
4
+ // app/assets/tailwind/studio_engine/theme.css (@theme), and every host gets
5
+ // them, the engine's @source scan and the motion layer from its one
6
+ // `@import "../builds/tailwind/studio_engine";` line. Nothing in the engine
7
+ // reads this file any more. A host still on `@config` + this preset builds
8
+ // exactly as before; test/integration/tailwind_preset_parity_test.rb holds it
9
+ // to the same utilities as theme.css until it goes.
10
+ //
11
+ // Migration (README, "Tailwind"): drop the host's `@config` line,
12
+ // config/tailwind.config.js, its safelist and lib/tasks/tailwindcss_engine_motion.rake.
13
+ // This file is removed in the release after every consumer has adopted.
14
+ //
1
15
  // Shared Tailwind config for all Studio apps
2
16
  // Apps spread from this in their own tailwind.config.js
3
17
 
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.114.1.rc1
4
+ version: 0.114.2.rc1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Alex McRitchie
@@ -371,6 +371,8 @@ files:
371
371
  - app/assets/stylesheets/studio/sticky_table_header.css
372
372
  - app/assets/tailwind/studio_engine/engine-motion.css
373
373
  - app/assets/tailwind/studio_engine/engine.css
374
+ - app/assets/tailwind/studio_engine/host.css
375
+ - app/assets/tailwind/studio_engine/theme.css
374
376
  - app/components/previews/studio/badge_component_preview.rb
375
377
  - app/components/previews/studio/badge_component_preview/schemes.html.erb
376
378
  - app/components/previews/studio/badge_component_preview/tones.html.erb
@@ -790,6 +792,7 @@ files:
790
792
  - lib/studio/survey.rb
791
793
  - lib/studio/survey/breakdown.rb
792
794
  - lib/studio/survey/export.rb
795
+ - lib/studio/tailwind_entry.rb
793
796
  - lib/studio/theme_resolver.rb
794
797
  - lib/studio/ui_primitives.rb
795
798
  - lib/studio/username_generator.rb