studio-engine 0.103.0.rc1 → 0.103.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: 773214119ffedd307e2f80ba808f5413cc69a2e9cb875c2997d7f7be0f120ce8
4
- data.tar.gz: 9b6c272fd88d22717b6e69020146c936d8db1d04b88f5a9b3effe5764d64ec38
3
+ metadata.gz: 3ed661773f8149123feb0d677eec217324c3346565fbab0bf27ff53c41a686cc
4
+ data.tar.gz: 5bd261c837ec5a372fc5f36451bef2082df9f4cb136791bb6b0ae359e60ccc77
5
5
  SHA512:
6
- metadata.gz: fd521d84d03c3c7a45e0fa1113d065db78a67ef5de36a8ac66b237b28b15a9cd562f8e703436b75f6419e927243d13ca99877f9d3493a59a6bf276b3b07cb269
7
- data.tar.gz: '0508739a475558e609a1efc465d75f2f37aa71fcf9c3b7477a759d3a900b4d1353628a150d8304f7a7b6bb568958cc97e1c8943382e57733a6466c8b803157f2'
6
+ metadata.gz: f3a4a14bf0ec9d85137401cdfad96443492cfa162565edd69ab0f2864390ec17ef3ad5447d32de895685c7f06ad85246cf9557bffe3167502e0ccfef5f275ed1
7
+ data.tar.gz: ecb32c2f49767010aaedd200cf39844922587419a03d22df781bf1ac1d744867b5762e44123a1c9b677b82706069bb66ab7d8eafc9b3f897318a2db0cb5ff3b8
data/CHANGELOG.md CHANGED
@@ -4,6 +4,45 @@ The format is [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). This pro
4
4
 
5
5
  ## Unreleased
6
6
 
7
+ ### Added
8
+
9
+ - **`--color-primary-ink` and `text-primary-ink`: the primary colour as chip
10
+ text.** The theme resolver derives it per theme the way it derives the
11
+ danger, warning and success inks: the configured primary, shaded until it
12
+ clears WCAG AA 4.5:1 on every surface and on its own 10%, 15% and 20% tints.
13
+ `text-primary` and `--color-primary` stay the brand colour.
14
+
15
+ ### Changed
16
+
17
+ - **Primary chips are painted in the primary colour and clear AA in both
18
+ themes.** `Studio::BadgeComponent`'s `tone: :primary` drew heading-coloured
19
+ text (white in dark, near-black in light), and its `scheme: "primary"`, the
20
+ Marketing chip on `/admin/emails` and the sidebar's ADMIN chip drew the raw
21
+ primary, 2.2:1 to 3.6:1 on its tint. All of them now draw `text-primary-ink`.
22
+ A host that copies the tone's class string (the hub's `status_tone`) changes
23
+ when it adopts the class.
24
+ - **Every role ink clears AA on its 10%, 15% and 20% tints, as a browser
25
+ paints them.** The apps put the inks on the stronger tints too (turf's
26
+ "Entered" badge is `bg-primary/15`; the hub's workflow and verdict chips are
27
+ `bg-success/20 text-success-ink` and `bg-warning/20 text-warning-ink`), where
28
+ an ink tuned to the 10% chip read as low as 3.83:1 (default warning, dark, on
29
+ 20%). The search now counts 0.10, 26/255 (how Chromium composites a 10%
30
+ fill), 0.15 and 0.20, each also one unit off in every channel, because 8-bit
31
+ compositing rounds (Chromium painted a 20% danger tint one unit lighter than
32
+ the blend, taking a 4.51:1 ink to 4.47:1). The inks darken in light and
33
+ lighten in dark. Default theme, 0.102.0 to this release:
34
+
35
+ | Ink | Dark | Light |
36
+ |---|---|---|
37
+ | primary (new) | `#BDB7FE` | `#58519D` |
38
+ | success | `#8EB791` to `#96BC99` | `#2E6C32` to `#2A622D` |
39
+ | warning | `#FF966C` to `#FFAE8D` | `#994A2B` to `#8F4528` |
40
+ | danger | `#F58F8F` to `#F79E9E` | `#AC3131` to `#9E2D2D` |
41
+
42
+ A configured role colour moves the same way; a configured colour that
43
+ already clears every tint (mcritchie-industries' yellow warning) keeps its
44
+ exact hex.
45
+
7
46
  ## 0.103.0 — 2026-10-10
8
47
 
9
48
  ### Added
@@ -20,7 +20,8 @@ module Studio
20
20
  # rest of the page uses for that status. Tokens only, so it reads in both themes.
21
21
  #
22
22
  # scheme: is the palette the components/badge partial took (SCHEMES), including
23
- # the stage-* pipeline ladder, and renders exactly what that partial rendered.
23
+ # the stage-* pipeline ladder. Its "primary" draws the primary ink; the rest
24
+ # render exactly what that partial rendered.
24
25
  # An unknown scheme renders FALLBACK, as the partial did.
25
26
  #
26
27
  # Pass tone: or scheme:, not both. Neither renders the neutral scheme.
@@ -34,7 +35,7 @@ module Studio
34
35
  success: "bg-success/10 text-success-ink border-success/40",
35
36
  warning: "bg-warning/10 text-warning-ink border-warning/40",
36
37
  danger: "bg-danger/10 text-danger-ink border-danger/40",
37
- primary: "bg-primary/10 text-heading border-primary/40",
38
+ primary: "bg-primary/10 text-primary-ink border-primary/40",
38
39
  muted: "bg-surface-alt text-muted border-subtle"
39
40
  }.freeze
40
41
 
@@ -44,7 +45,7 @@ module Studio
44
45
  "warning" => "bg-yellow-500/10 text-yellow-400 border-yellow-500/30",
45
46
  "info" => "bg-blue-500/10 text-blue-500 border-blue-500/30",
46
47
  "violet" => "bg-violet/10 text-violet border-violet/30",
47
- "primary" => "bg-primary/10 text-primary border-primary/30",
48
+ "primary" => "bg-primary/10 text-primary-ink border-primary/30",
48
49
  "orange" => "bg-orange-500/10 text-orange-500 border-orange-500/30",
49
50
  "emerald" => "bg-emerald-500/10 text-emerald-500 border-emerald-500/30",
50
51
  "gray" => "bg-gray-500/10 text-gray-400 border-gray-500/30",
@@ -27,7 +27,7 @@
27
27
  <div class="px-4 pt-4 pb-1 label-upper font-bold text-muted flex items-center gap-2">
28
28
  <span><%= section[:title] %></span>
29
29
  <% if section[:admin] %>
30
- <span class="rounded border border-primary/20 bg-primary/10 px-1.5 py-0.5 text-[10px] font-mono text-primary">ADMIN</span>
30
+ <span class="rounded border border-primary/20 bg-primary/10 px-1.5 py-0.5 text-[10px] font-mono text-primary-ink">ADMIN</span>
31
31
  <% end %>
32
32
  </div>
33
33
 
@@ -92,7 +92,7 @@
92
92
  <% if sidebar_admin && sections.any? { |section| section[:admin] } %>
93
93
  <div class="px-4 pt-4 pb-1 label-upper font-bold text-muted flex items-center gap-2">
94
94
  <span>Geo</span>
95
- <span class="rounded border border-primary/20 bg-primary/10 px-1.5 py-0.5 text-[10px] font-mono text-primary">ADMIN</span>
95
+ <span class="rounded border border-primary/20 bg-primary/10 px-1.5 py-0.5 text-[10px] font-mono text-primary-ink">ADMIN</span>
96
96
  </div>
97
97
  <%= render "components/geo_signpost", variant: :sidebar, close_action: close_action %>
98
98
  <% end %>
@@ -85,7 +85,7 @@
85
85
  <%= link_to entry.label, admin_email_path(entry.key),
86
86
  class: "font-semibold text-heading hover:text-primary underline-offset-2 hover:underline" %>
87
87
  <% if entry.marketing? %>
88
- <span class="badge bg-primary/10 text-primary border-primary/30 ml-1.5 whitespace-nowrap">Marketing</span>
88
+ <span class="badge bg-primary/10 text-primary-ink border-primary/30 ml-1.5 whitespace-nowrap">Marketing</span>
89
89
  <% end %>
90
90
  <% if entry.description.present? %>
91
91
  <p class="text-sm text-body mt-0.5 max-w-md"><%= entry.description %></p>
@@ -103,7 +103,7 @@
103
103
  </div>
104
104
 
105
105
  <div class="flex flex-wrap items-center gap-2 mt-3">
106
- <span class="badge <%= @entry.marketing? ? "bg-primary/10 text-primary border-primary/30" : "bg-inset text-muted border-subtle" %> whitespace-nowrap">
106
+ <span class="badge <%= @entry.marketing? ? "bg-primary/10 text-primary-ink border-primary/30" : "bg-inset text-muted border-subtle" %> whitespace-nowrap">
107
107
  <%= @entry.type.to_s.titleize %>
108
108
  </span>
109
109
  <span class="<%= badge_class %> whitespace-nowrap"><%= badge_label %></span>
@@ -124,7 +124,7 @@
124
124
  </div>
125
125
  <% end %>
126
126
  <% chrome_kickoff = capture do %>
127
- <span class="inline-flex items-center gap-1 rounded border border-primary/30 bg-primary/10 px-1.5 py-0.5 text-2xs font-semibold uppercase tracking-wide text-primary">kickoff slot</span>
127
+ <span class="inline-flex items-center gap-1 rounded border border-primary/30 bg-primary/10 px-1.5 py-0.5 text-2xs font-semibold uppercase tracking-wide text-primary-ink">kickoff slot</span>
128
128
  <% end %>
129
129
  <%
130
130
  # Per-column count_class colours (raw classes, the task_stage_count_classes shape),
@@ -117,6 +117,10 @@ module Studio
117
117
  "--color-danger-ink" => status_ink(danger, surfaces, direction: :lighten),
118
118
  "--color-warning-ink" => status_ink(warning, surfaces, direction: :lighten),
119
119
  "--color-success-ink" => status_ink(success, surfaces, direction: :lighten),
120
+ # The primary role as chip TEXT (`bg-primary/10 text-primary-ink`): the
121
+ # brand hue, shaded per theme until it clears AA on the surfaces and on
122
+ # its own tint. --color-primary itself stays the brand colour.
123
+ "--color-primary-ink" => status_ink(primary, surfaces, direction: :lighten),
120
124
  "--color-accent" => colors[:accent] || "#F72585"
121
125
  }.merge(fill_vars(primary, warning, danger))
122
126
  end
@@ -165,6 +169,16 @@ module Studio
165
169
  # How strong a status role's tint is where its ink is read on it: the
166
170
  # `bg-<role>/10 text-<role>-ink` badge and flash-panel pattern.
167
171
  STATUS_TINT = 0.10
172
+ # Every tint strength a role's ink is read on. The 10% chip, as written and
173
+ # as an 8-bit alpha channel holds it (26/255, a little stronger), and the
174
+ # 15% and 20% tints the apps also put an ink on: a selected or "Entered"
175
+ # badge (`bg-primary/15`), the hub's workflow and verdict chips
176
+ # (`bg-success/20 text-success-ink`, `bg-warning/20 text-warning-ink`),
177
+ # turf's signer roster (`bg-danger/15 text-danger-ink`). A stronger tint
178
+ # sits closer to the ink, so an ink tuned to the 10% chip alone read 3.89:1
179
+ # on a 20% one (default warning, dark). 0.15 and 0.20 are the stronger of
180
+ # each written alpha and its 8-bit value (38/255 is weaker, 51/255 equal).
181
+ TINT_ALPHAS = [STATUS_TINT, (STATUS_TINT * 255).round / 255.0, 0.15, 0.20].freeze
168
182
 
169
183
  # A status ink lands on the page surfaces AND on its own role's tint over
170
184
  # each of them (the `bg-warning/10 text-warning-ink` badge). The tint is
@@ -172,9 +186,28 @@ module Studio
172
186
  # to the bare surfaces alone comes out below AA inside its own badge
173
187
  # (default theme: warning 3.91:1 dark, success 4.00:1 dark, danger 4.10:1
174
188
  # light and 4.18:1 dark). Counting the tints as surfaces closes that.
189
+ #
190
+ # The tint is counted at every strength in TINT_ALPHAS. Chromium composites
191
+ # a 10% fill at 26/255, a little stronger than 0.10, and an ink tuned to
192
+ # 0.10 alone measured 4.48:1 on the painted pixels (danger, dark, on a
193
+ # #141516 base).
194
+ #
195
+ # Each tint is also counted one unit lighter and one unit darker in every
196
+ # channel. A browser composites in 8 bits and rounds the fill's share and
197
+ # the surface's share separately, so a painted tint can sit one unit off
198
+ # the exact blend: the default danger at 20% over #3C3853 blends to
199
+ # #5F3A50 and Chromium paints #603B50, which took an ink graded 4.51:1 here
200
+ # to 4.47:1 on screen. Contrast moves with luminance, and luminance with
201
+ # each channel, so the two all-channel neighbours bound every rounding.
175
202
  def status_ink(role, surfaces, direction:)
176
- tints = surfaces.map { |bg| ColorScale.blend(role, bg, STATUS_TINT) }
177
- contrast_ink(role, direction: direction, start: 0.0, target: 4.5, against: surfaces + tints)
203
+ tints = TINT_ALPHAS.flat_map { |alpha| surfaces.map { |bg| ColorScale.blend(role, bg, alpha) } }
204
+ painted = tints.flat_map { |tint| [tint, nudge(tint, 1), nudge(tint, -1)] }
205
+ contrast_ink(role, direction: direction, start: 0.0, target: 4.5, against: surfaces + painted)
206
+ end
207
+
208
+ # `hex` with every channel moved `units` (clamped to 0..255).
209
+ def nudge(hex, units)
210
+ ColorScale.rgb_to_hex(*ColorScale.hex_to_rgb(hex).map { |c| c + units })
178
211
  end
179
212
 
180
213
  # The lightest shade of `color` that carries a white label at AA: the
@@ -307,6 +340,7 @@ module Studio
307
340
  "--color-danger-ink" => status_ink(danger, surfaces, direction: :darken),
308
341
  "--color-warning-ink" => status_ink(warning, surfaces, direction: :darken),
309
342
  "--color-success-ink" => status_ink(success, surfaces, direction: :darken),
343
+ "--color-primary-ink" => status_ink(primary, surfaces, direction: :darken),
310
344
  "--color-accent" => colors[:accent] || "#F72585"
311
345
  }.merge(fill_vars(primary, warning, danger))
312
346
  end
@@ -1,3 +1,3 @@
1
1
  module Studio
2
- VERSION = "0.103.0.rc1"
2
+ VERSION = "0.103.0"
3
3
  end
@@ -103,6 +103,9 @@ module.exports = {
103
103
  'danger-ink': 'var(--color-danger-ink)',
104
104
  'warning-ink': 'var(--color-warning-ink)',
105
105
  'success-ink': 'var(--color-success-ink)',
106
+ // The primary role as chip text, derived the same way. text-primary
107
+ // stays the brand colour for links, icons and large text.
108
+ 'primary-ink': 'var(--color-primary-ink)',
106
109
  },
107
110
  // The status-role FILLS: badge tints, flash panels, borders. Registered
108
111
  // for backgrounds and borders ONLY (not `colors`), so that registering
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.103.0.rc1
4
+ version: 0.103.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Alex McRitchie