its-swiss 0.1.0 → 0.4.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: 844fa90d3877d55f3c22b850b00bedfe6156241216b1165aad929ad3540edbd8
4
- data.tar.gz: 470159b689994419fb692d26c0d1ede1cd48c73508090a4cb9f472ffa95788cc
3
+ metadata.gz: d30726333f8265818004958fdb2276a6421ee240b3fbc0b7fc977adf1f39ddb3
4
+ data.tar.gz: 73e52320a14d39e0cb6da2fc22c8b55de7a57167d746a650232c603d12a7d7fc
5
5
  SHA512:
6
- metadata.gz: 40e2d6de3e683bc1adffbaabefd3863e3886ce7462c4e071ed916ff45a743f08b1d590535b6c0732821fa56017299d182045c631da1a1e30a9f84e3c00b5d666
7
- data.tar.gz: e3ef3c8e93c892330619ad27ac091a0e9a3e9f3b8613db69dda8ed5ddf0da0548e406f83912770a1cfd010612380481906060cc8163aebc85d682846d7b20e52
6
+ metadata.gz: b8c711d2e82584e464c4bc47777a6340a118d53165e20664aff36ab5a43b582273dc60e7f13bbeb55de368054f9e7e9924d637e164d10608db09f1ef97f38631
7
+ data.tar.gz: 6dc95c59d9c700ac68c18c2f8915e0a44e2a79d3701fdaa30ea579ec024cb39a65379498ee40f778f09fa2d3575b3830f6c4eec9e626c586d6e1e443acb71ee0
data/CHANGELOG.md CHANGED
@@ -2,6 +2,232 @@
2
2
 
3
3
  Semver. Consumers pin `~> 0.1`.
4
4
 
5
+ ## 0.4.0 — 2026-08-31
6
+
7
+ **Breaking.** The baseline is now the line, not a third of it. Applications
8
+ setting `--baseline` need to set `--line` instead; everything else follows.
9
+
10
+ ### The grid
11
+
12
+ 0.3.0 put the type on a baseline grid and then registered it to eight pixels —
13
+ a third of the body line. A block could be a whole number of thirds and still
14
+ land every line of type after it somewhere new, which is what a label on a
15
+ sixteen-pixel leading and a section head on thirty-two did, all the way down a
16
+ column. A third of a line is a spacing unit. It is not a baseline.
17
+
18
+ - **`--line` (24px) replaces `--baseline` (8px)** as the interval everything
19
+ vertical registers to. It is also the body leading, because in a baseline
20
+ grid those are one number.
21
+ - **Every leading is a whole number of lines.** `h1` and `h2` move from 40 and
22
+ 32 to 48; the page title from 48 to 72; labels and captions from 16 to 24.
23
+ The page is airier, and that is what the convention looks like.
24
+ - **`--line-2`, `--line-3`, `--line-4`, `--line-6`** for whole-line spacing.
25
+ `--space-*` survives, renamed at its root to `--space-unit`, as the
26
+ **horizontal** step: an inline gap has no baseline to miss.
27
+ - **`--half-line` and `.subgrid`**, the one subgrid, for a block of small
28
+ type. It halves `--line` for the block's *children* — the block's own
29
+ margins belong to the column outside it and are owed whole lines. Declaring
30
+ it on the block itself halved the gap above it and landed the column half a
31
+ line out, which the specimen caught.
32
+
33
+ ### Pictures, rules, controls
34
+
35
+ - **`.figure` puts a picture on the line at any width.** Its box is the
36
+ natural height taken up to the next whole line with `round()`, recomputed as
37
+ the container resizes; the picture is fitted inside with `contain`, so
38
+ nothing of it is lost. `.figure--cover` crops instead. `--ratio` is the
39
+ application's to declare. Tested at four widths.
40
+ - `hr` is a line-tall box rather than a rule with margins either side.
41
+ - A text control is two lines of box: one the text sits on, one the rule
42
+ closes. A checkbox is reset out of that, since the browser draws it at a
43
+ size of its own.
44
+
45
+ ### Guards
46
+
47
+ - The box test measures in lines, and honours `.subgrid` — a block that
48
+ declares a half-line may use one; the block itself still owes whole lines.
49
+ - A picture is measured at 1400, 1100, 903 and 712 pixels wide.
50
+ - `--baseline` may not reappear in the library. A token that no longer means
51
+ what it says is worse than one that is gone.
52
+
53
+ ## 0.3.0 — 2026-09-01
54
+
55
+ The vertical rhythm, twice: once to put every box on the baseline, and again
56
+ to put the type on it. Found from outside, in a page that embedded the
57
+ specimen and turned the baseline overlay on — the grid was drawn correctly and
58
+ nothing sat on it.
59
+
60
+ Rendering moves. Every ruled component is a pixel or two shorter, and where a
61
+ browser can trim a text box the leading comes out of the block, so a page is
62
+ tighter than 0.2.0 by a few pixels per block. Read this section before
63
+ upgrading.
64
+
65
+ ### Fixed
66
+
67
+ - **The vertical rhythm was decorative.** Every line box was a whole number of
68
+ baselines and the page was still off the grid, because a line box is not a
69
+ box: wherever a rule sat on a padded edge, its width was *added* to a box
70
+ whose padding was already whole baselines. A hairline is a pixel, so each
71
+ one moved everything below it by a pixel — and since the masthead, the
72
+ footer, `hr`, every table row, every field control, every button, the
73
+ pagination and the errors block all did it, the error accumulated down the
74
+ column rather than showing up once. On the specimen the page was one pixel
75
+ out by the first heading and seven by the footer.
76
+
77
+ Every rule now comes out of the padding it sits on — `calc(var(--space-1) -
78
+ var(--rule-hair))` — and `hr` draws its rule on the top edge of a box one
79
+ baseline tall instead of compensating in a margin, because a margin
80
+ compensation collapses away exactly where a rule leads or precedes a
81
+ section. Boxes that change height: a button and a table row are 40px rather
82
+ than 42 and 41, a field control 32 rather than 33, a `select` 32 rather than
83
+ 29.
84
+ - **A token in a line of prose grew the line.** `code`, `kbd` and `samp` set
85
+ in the mono stack are a second font on the line, with an ascent and a
86
+ descent of their own that the line box had to hold — a pixel at body size,
87
+ four under the page title, and only on the lines that happened to mention a
88
+ token. Their leading is now zero, which leaves the line box to the strut;
89
+ the glyphs are untouched.
90
+ - **`select` ignored its leading.** `font: inherit` hands a control the family
91
+ and the size and puts its line box back to `normal`, so a select stood 29px
92
+ where every other control stood 32. It is now given the height the rest of
93
+ the ladder produces.
94
+ - **A checkbox row was 20px.** The label beside a checkbox took the micro
95
+ register's 16px line box, which is not the row a 13px control sits on. It
96
+ now takes the same 24px line the library already gives the micro type you
97
+ can tap.
98
+ - **Table rows landed on half pixels.** With `border-collapse: collapse` the
99
+ shared rule belongs to the boundary rather than to either cell, and the
100
+ browser splits it: every row sat at `x.5` and the header row was half a
101
+ pixel short. Nothing in the library draws a border two cells could share, so
102
+ borders are separate — with the spacing still zero — and a row is exactly as
103
+ tall as the ladder says.
104
+
105
+ ### Added
106
+
107
+ - **The type sits on the baseline now, not merely in step with it.** Boxes in
108
+ step are not a baseline grid. Where a line's baseline falls inside its line
109
+ box depends on the font's ascent and the leading either side of it, so at
110
+ 0.2.0 a paragraph's baselines sat a pixel off the grid, a caption's four,
111
+ and the two were three pixels out of register with each other — every
112
+ register keeping its own grid, none of them the page's.
113
+
114
+ Every text register is now trimmed — `text-box: trim-both cap alphabetic` —
115
+ which makes a block's over edge the cap of its first line and its under edge
116
+ the baseline of its last. What then separates the box from the grid is the
117
+ cap height, and one padding rounds it up: `calc(round(up, 1cap,
118
+ var(--baseline)) - 1cap)`, measured by the browser in cap units, so a
119
+ library that leaves the typeface to the application still never has to be
120
+ told the font's metrics. It is published as `--cap-correction`, which any
121
+ component setting its own padding adds to it — `calc(var(--space-1) +
122
+ var(--cap-correction))` is the right padding in both paths.
123
+
124
+ Behind `@supports`, and the fallback is the box rhythm below: Chromium and
125
+ Safari trim, Firefox does not yet. **The two do not render identically.**
126
+ Trimming takes the leading out of a block's own box, so the spacing you
127
+ declare is the spacing you see; a trimmed page is a few pixels tighter per
128
+ block than the same page in Firefox, and the ladder means what it says
129
+ rather than what it says plus half a line. Both stay on the grid.
130
+
131
+ Controls opt out — a button is five baselines of box with its label centred,
132
+ and trimming the label would take the box with it — as does any block whose
133
+ content is not type. `text-box: normal; padding-block-start: 0` is how.
134
+
135
+ ### Fixed
136
+
137
+ - **`rake release` did not exist.** The release workflow runs
138
+ `bundle exec rake release`, which is Bundler's task and arrives with
139
+ `require "bundler/gem_tasks"` — which the Rakefile did not have. So a
140
+ release passed every check it makes, minted its credentials over OIDC, and
141
+ stopped at *Don't know how to build task 'release'*. 0.2.0 never reached
142
+ RubyGems for this reason, and 0.1.0 only did because it was pushed by hand
143
+ before the workflow existed, which the "already published" guard then read
144
+ as nothing to do. Guarded now, along with the workflow's own tag-versus-
145
+ gemspec check.
146
+ - **Every browser test was skipping in CI.** The suite looks for a browser and
147
+ a driver, and a candidate found on the `PATH` came back as the bare name it
148
+ was looked up by. Selenium wants a file — given `"chromedriver"` it raises
149
+ *not a file* — so the harness caught that, fell back to `rack_test` and
150
+ skipped, on a runner that had just installed a driver for it. The job was
151
+ green and had checked nothing: 12 runs, 12 skips. Candidates now resolve to
152
+ where they actually are, and the CI job now names the browser and the driver
153
+ the same step installed — the runner image ships a chromedriver of its own,
154
+ and a driver a major version ahead of the browser refuses to start a session
155
+ at all — and sets `REQUIRE_BROWSER`, which turns a skip into a failure — a skipped browser test is invisible in a passing
156
+ job, and the assertions that need a browser are the ones about what the
157
+ cascade and the box model actually did.
158
+ - **The `:head` slot rendered before the library's own stylesheets**, so a
159
+ layer named there took its place in the order ahead of every layer the
160
+ library declares and lost to all of them. The specimen's own furniture is
161
+ one such stylesheet, and it could not override the library it documents. The
162
+ slot now comes after.
163
+
164
+ ### Guards added
165
+
166
+ - **Every register's last baseline is on a baseline**, measured in the
167
+ browser. Under `trim-both … alphabetic` a block's under edge *is* that
168
+ baseline, so measuring the box measures the type — and it is measured rather
169
+ than probed on purpose: inserting a span to read a baseline re-lays out a
170
+ trimmed page and moves the thing it was measuring. Skips where the browser
171
+ cannot trim.
172
+ - **The correction is zero where the browser cannot trim**, read from the
173
+ source, so the enhancement can never become a requirement.
174
+ - **Every box on the specimen starts on a baseline and is a whole number of
175
+ them tall**, measured in the browser on the page that holds one of
176
+ everything. The suite already asserted that every *line box* was a whole
177
+ number of baselines, which was true throughout and is what let all of the
178
+ above through.
179
+ - The leading guard now admits `0`, the one value that is not a measurement:
180
+ an inline box that takes no part in the line it sits on.
181
+
182
+ ## 0.2.0 — 2026-08-31
183
+
184
+ Everything here was found by the second consumer inside a day of building on
185
+ 0.1.0, which is roughly the point of having one.
186
+
187
+ ### Fixed
188
+
189
+ - **The pagination partial printed its own source.** Its opening comment held
190
+ a worked example written in ERB; the example's own closing delimiter ended
191
+ the comment, and the two lines after it were emitted as page content. On
192
+ every page that rendered it — including the specimen, the library's own
193
+ documentation and regression fixture, for the whole of 0.1.0. The example
194
+ now lives in the README, where it can be quoted.
195
+ - **`.table .numeric` zeroed the end padding of every numeric cell**, not just
196
+ one in the final column, because it set the padding with the `padding-inline`
197
+ shorthand. Anywhere else in a table the next column's text began exactly
198
+ where the number ended. Now sets only `padding-inline-start`, and leaves the
199
+ final column to the `:last-child` rule that already handled it.
200
+
201
+ ### Changed
202
+
203
+ - **`its_swiss:install` generates a nested layout** — `app/views/layouts/
204
+ application.html.erb` ending in `render template: "layouts/its_swiss/shell"`
205
+ — instead of injecting `layout "its_swiss/shell"` into `ApplicationController`.
206
+ The shell is filled through `content_for`, which has to run while a view is
207
+ rendering; naming it on the controller rendered the shell but left nowhere
208
+ to fill it once, so an application wrote its masthead into every view. The
209
+ generated layout also **links `theme.css`**, which nothing did before: an
210
+ application that followed the README exactly got no accent and no grid, with
211
+ no error anywhere.
212
+
213
+ Existing installs keep working. To move: delete the `layout` line from
214
+ `ApplicationController` and add the layout, or re-run the generator.
215
+
216
+ ### Guards added
217
+
218
+ Three of the four findings were in views, a generator and a form builder —
219
+ surfaces a CSS library's tests do not reach. Two new ones, both of which fail
220
+ on the bugs above:
221
+
222
+ - The rendered specimen contains no template delimiters. Every other assertion
223
+ in that file was about markup that should be present; none about output that
224
+ should not be.
225
+ - No view's comment quotes ERB. A comment ends at the first closing delimiter
226
+ it meets, so an example inside one is an example that truncates it.
227
+
228
+ The specimen's table now has a numeric column that is not last, and a browser
229
+ test measures its end padding — the case that broke.
230
+
5
231
  ## 0.1.0 — 2026-08-31
6
232
 
7
233
  The first extraction, from Pandatone, which ships on it.
@@ -53,5 +279,7 @@ They are in the boundary the handoff drew and they break the library's own
53
279
  rule about two applications; they are the two components most likely to move
54
280
  when Stripeclub lands.
55
281
 
282
+ *(Both of them shipped broken. See 0.2.0.)*
283
+
56
284
  `.footer` is the same shape of guess — the shell has the slot and Pandatone
57
285
  does not fill it.
data/README.md CHANGED
@@ -14,7 +14,7 @@ the consuming application's to set — it is not a theme with the colour left
14
14
  out, it is a style whose argument is that the values are enough.
15
15
 
16
16
  ```ruby
17
- gem "its-swiss"
17
+ gem "its-swiss", "~> 0.1"
18
18
  ```
19
19
 
20
20
  ```sh
@@ -62,7 +62,67 @@ holding all of them. Nothing here has a default the gem could pick honestly.
62
62
  | `--accent`, `--accent-ink` | State and emphasis only. Unset, the accent is ink |
63
63
  | `--font-family` | The typeface. The gem ships none — declare `@font-face` and name it |
64
64
  | `--value-chroma`, `--value-hue` | Warms the whole value scale together. Neutral as shipped |
65
- | `--columns`, `--gutter`, `--baseline` | How many fields this problem has, and the unit everything vertical is measured in |
65
+ | `--columns`, `--gutter` | How many fields this problem has |
66
+ | `--line` | The baseline: the interval everything vertical registers to |
67
+ | `--ratio` | A picture's aspect ratio, per figure — the library cannot read one |
68
+ | `--cap-correction` | Read, not set: what a trimmed register adds above its cap to reach the next baseline |
69
+
70
+ ### The baseline
71
+
72
+ `--line` (24px) is the interval everything vertical registers to, and the
73
+ leading of the body text. In this style those are one number, because that is
74
+ what a baseline grid is: Müller-Brockmann's horizontal lines are one line of
75
+ text apart and a field is a whole number of them.
76
+
77
+ Every leading is `var(--line)` or a whole multiple of it — `--line-2`,
78
+ `--line-3`, `--line-4`, `--line-6`. Every margin, padding and gap on the
79
+ vertical axis is too. `--space-*` survives as the **horizontal** step: an
80
+ inline gap has no baseline to miss.
81
+
82
+ Boxes in step are only the easy half. Where a line's baseline falls inside its
83
+ line box depends on the font's ascent and the leading either side of it, so a
84
+ caption and a paragraph can both be in step and still be out of register with
85
+ each other. So every text register is trimmed to its own type: `text-box:
86
+ trim-both cap alphabetic` makes a block's over edge the cap of its first line
87
+ and its under edge the baseline of its last, and one padding rounds the cap
88
+ height up to the next line —
89
+
90
+ ```css
91
+ padding-block-start: calc(round(up, 1cap, var(--line)) - 1cap);
92
+ ```
93
+
94
+ — which the browser computes in cap units, so the library still never has to
95
+ be told the font's metrics. It is published as `--cap-correction`: a component
96
+ that sets its own padding on trimmed text adds it, as
97
+ `calc(var(--half-line) + var(--cap-correction))`, which is the right padding
98
+ whether or not the browser trims.
99
+
100
+ Two things opt out, with `text-box: normal; padding-block-start: 0`: a control,
101
+ whose box is the target and whose label is centred in it, and any block whose
102
+ content is not type — an image, a swatch, a diagram.
103
+
104
+ It is behind `@supports`, and the fallback is the box rhythm above. Chromium
105
+ and Safari trim; Firefox does not yet. The two do not render identically:
106
+ trimming takes the leading out of a block's own box, so a trimmed page is a
107
+ few pixels tighter per block. Both stay on the grid.
108
+
109
+ #### The one subgrid
110
+
111
+ A block of small type may sit on a half-line — a dense run of captions, a
112
+ table of figures:
113
+
114
+ ```html
115
+ <div class="subgrid">…</div>
116
+ ```
117
+
118
+ It halves `--line` for the block's **children**, and the leadings, the spacing
119
+ and the cap correction all follow. The children, not the block: a block's own
120
+ margins belong to the column outside it and are owed whole lines. Set on the
121
+ block itself it halves the gap above it and lands the column half a line out.
122
+
123
+ A block, and only a block. An inline `<small>` shares its paragraph's line and
124
+ must not change it.
125
+
66
126
 
67
127
  ### The value scale
68
128
 
@@ -98,6 +158,27 @@ Within three units a channel, which is the cost of the four hand-picked
98
158
  values becoming one ladder with a single chroma and a single hue. Measured in
99
159
  Chromium, not calculated.
100
160
 
161
+ ### Pictures
162
+
163
+ The one case that nearly does not transfer from print. A picture's height is
164
+ its fluid width over its ratio, so without help one picture puts the whole
165
+ column below it off the grid at every width but a few.
166
+
167
+ ```html
168
+ <figure class="figure" style="--ratio: 1.618">
169
+ <img src="…" alt="…">
170
+ <figcaption class="micro">…</figcaption>
171
+ </figure>
172
+ ```
173
+
174
+ The box is the picture's natural height taken up to the next whole line, by
175
+ `round()`, recomputed as the container resizes. The picture is fitted inside
176
+ it — `contain` by default, because a library must not crop an image it did not
177
+ choose. `.figure--cover` crops instead, which is the Müller-Brockmann move and
178
+ yours to make where you own the picture.
179
+
180
+ `--ratio` is yours to declare; CSS cannot read an intrinsic one.
181
+
101
182
  ### The grid
102
183
 
103
184
  The gem ships primitives and never a grid.
@@ -126,12 +207,28 @@ it stops on a field line:
126
207
 
127
208
  ## The layout shell
128
209
 
129
- ```ruby
130
- class ApplicationController < ActionController::Base
131
- layout "its_swiss/shell"
132
- end
210
+ The installer writes `app/views/layouts/application.html.erb` as a layout
211
+ **for** the shell rather than one instead of it:
212
+
213
+ ```erb
214
+ <% content_for :head do %>
215
+ <%= stylesheet_link_tag "theme", "data-turbo-track": "reload" %>
216
+ <% end %>
217
+
218
+ <% content_for :mark do %><%= link_to "Your app", root_path %><% end %>
219
+
220
+ <%= render template: "layouts/its_swiss/shell" %>
133
221
  ```
134
222
 
223
+ Nested, not `layout "its_swiss/shell"` on a controller. `content_for` has to
224
+ run while a view is rendering, and these slots are set once for the whole
225
+ application — naming the shell on the controller renders it but leaves
226
+ nowhere to fill it, so every view ends up writing the same masthead.
227
+
228
+ Note the `:head` slot links `theme.css`. The shell links the library's six
229
+ stylesheets and stops; the accent and the grid live in yours, and nothing
230
+ links it but this.
231
+
135
232
  Slots, all optional:
136
233
 
137
234
  | Slot | |
@@ -157,6 +254,19 @@ beyond the shell is the application's, for the same reason its grid is.
157
254
  | `its_swiss_form_with(...)` | `form_with`, already holding the library's builder |
158
255
  | `its_swiss_page_numbers(page, pages)` | Which numbers a run of them shows, elided |
159
256
 
257
+ ### Pagination
258
+
259
+ The library has no paginator and no opinion about which one you use — it takes
260
+ a page, a total, and something that turns a number into a URL:
261
+
262
+ ```erb
263
+ <%= render "its_swiss/shared/pagination",
264
+ page: @page, pages: @pages, url: ->(n) { colors_path(page: n) } %>
265
+ ```
266
+
267
+ Long runs are elided around the current page. `window:` (default 2) sets how
268
+ many neighbours show; `label:` names the `<nav>` for a screen reader.
269
+
160
270
  ## The form builder
161
271
 
162
272
  One shape for every field: a label, a control, and — when there is something to
@@ -211,10 +321,11 @@ every line box is measured in baselines, that no signal rests on colour alone.
211
321
  The rest needs a browser, because a rule on the wrong selector reads correctly
212
322
  in the CSS and does nothing on a page. Those assert what Chromium actually
213
323
  resolved: that an unlayered declaration beats the library's layered one, that
214
- the accent unset is ink, that quiet ink clears 4.5:1, that every line box is a
215
- whole number of baselines, that nothing escapes the page at 390px, and that the
216
- measure lands on a field line the browser really laid out. They skip loudly
217
- rather than pretending to have checked.
324
+ the accent unset is ink, that quiet ink clears 4.5:1, that every box on the
325
+ specimen starts on a baseline and is a whole number of them tall, that every
326
+ register's last baseline lands on one, that nothing escapes the page at 390px,
327
+ and that the measure lands on a field line the browser really laid out. They
328
+ skip loudly rather than pretending to have checked.
218
329
 
219
330
  There are no pixel tests.
220
331
 
@@ -230,7 +341,8 @@ ladder; the names are the library's rather than the application's.
230
341
  | `--ink-quiet`, `--ink` | unchanged |
231
342
  | `--accent` | unchanged — still the application's to set |
232
343
  | `--font` | `--font-family` |
233
- | `--size-1..5`, `--space-N`, `--baseline`, `--measure`, `--page-max` | unchanged |
344
+ | `--size-1..5`, `--space-N`, `--measure`, `--page-max` | unchanged |
345
+ | `--baseline` (8px) | **gone.** `--line` (24px) is the baseline now — see 0.4.0 |
234
346
  | `.masthead__nav` | `.nav` |
235
347
  | `.channels` | `.pairs` |
236
348
  | `.form`, `.field`, `.button*`, `.copy`, `.errors`, `.hint`, `.empty` | unchanged |