Skip to main content

max / makeover-geometry

Throw out the Touch density preset The Touch numbers were derived rather than quoted, and they put a floor under the Pointer preset that the HIG numbers never asked for: Touch section at 16 plus the no-inversion invariant means Touch pane >= 16, which forces Pointer pane above 16. The retune to pane 14 / page 16 could not land while they stood. So the preset goes and the axis stays. Density::Touch falls through to Pointer's steps, and touch_has_no_preset_of_its_own asserts exactly that, so re-deriving one has to come to the test and say so. Bound keeps its arm: it was never derived. density_css still emits the coarse-pointer block, now a no-op repeat of the base layer and commented as such. Removing it would break the selection mechanism makeover-build materialises into goingson and balanced_breakfast, and keeping it wired is what makes a re-derived preset a rebuild rather than a migration.
Co-Authored-By
Claude Opus 5 (1M context) <noreply@anthropic.com>
Author: Max Johnson <me@maxj.phd> · 2026-07-30 01:45 UTC
Signed with PGP, not checked
Commit: 807d278ac9484735118dc739d08bef6f14c5db18
Parent: 7f54f3f
1 file changed, +52 insertions, -46 deletions
M src/lib.rs +52 -46
@@ -47,14 +47,17 @@
47 47 //! The mobile and desktop builds of a Tauri app should differ mostly by which
48 48 //! preset they emit, not by a parallel set of hand-written mode-scoped rules.
49 49 //!
50 - //! The two presets are not one scaled copy of the other, and the asymmetry is
51 - //! the point — it is also why a base scalar alone cannot express this. Touch
52 - //! needs *more* room between things you tap, because a fingertip is coarser
53 - //! than a pointer, and *less* room around the edges, because the screen is
54 - //! small and outer margin is screen you do not get to use. So `Peer` and
55 - //! `Section` open up under [`Density::Touch`] while `Pane` and `Page` tighten.
56 - //! `Bound` never moves: it is the one relationship that says "these are one
57 - //! object", and separating them on touch would say the opposite.
50 + //! **[`Density::Touch`] has no preset of its own right now.** It resolves to
51 + //! the same steps as [`Density::Pointer`] until one is derived. The previous
52 + //! Touch numbers were thrown out on 2026-07-29: they opened `Peer` and
53 + //! `Section` while tightening `Pane` and `Page`, which put Touch's `Section` at
54 + //! 16 and made any Pointer `Pane` at or below 16 an inversion. That blocked the
55 + //! Pointer retune to `pane 14 / page 16` and the values were derived rather
56 + //! than quoted, so the preset goes rather than the retune. The axis stays: the
57 + //! [`Density`] argument, [`density_css`] and every consumer's mode-scoped block
58 + //! are all still wired, and a re-derived preset lands here without touching a
59 + //! call site. That is the claim this section opens with, demonstrated the hard
60 + //! way.
58 61 //!
59 62 //! # Surfaces, and why a TUI is not a third density
60 63 //!
@@ -259,7 +262,9 @@
259 262 /// Mouse or trackpad. Resolves to the Mac OS 8 HIG's own proportions.
260 263 #[default]
261 264 Pointer,
262 - /// Finger. Targets separate, shells tighten.
265 + /// Finger. **No preset of its own yet**: resolves to [`Self::Pointer`]'s
266 + /// steps until one is derived. See the crate-level "Density presets"
267 + /// section for why the previous one was thrown out.
263 268 Touch,
264 269 }
265 270
@@ -382,27 +387,24 @@
382 387
383 388 impl Gap {
384 389 /// The step this relationship resolves to at a given density.
390 + ///
391 + /// Only [`Density::Pointer`] has values, and they are the HIG's own.
392 + /// [`Density::Touch`] was thrown out on 2026-07-29 and falls through to the
393 + /// same steps; the `density` argument is kept because the axis is still
394 + /// real, and re-deriving Touch means adding arms here and nothing else.
385 395 #[must_use]
386 396 pub const fn step_at(self, density: Density) -> Step {
387 - match (self, density) {
388 - // Binding is not a separation, so it does not open up on touch.
389 - (Self::Bound, _) => Step::Tight,
397 + let _ = density;
398 + match self {
399 + // Binding is not a separation, so it would not open up on touch
400 + // either. The one arm a re-derived preset should leave alone.
401 + Self::Bound => Step::Tight,
390 402
391 - (Self::Peer, Density::Pointer) => Step::Snug,
392 - (Self::Peer, Density::Touch) => Step::Roomy,
393 -
394 - (Self::Group, Density::Pointer) => Step::Roomy,
395 - (Self::Group, Density::Touch) => Step::Wide,
396 -
397 - (Self::Section, Density::Pointer) => Step::Wide,
398 - (Self::Section, Density::Touch) => Step::Loose,
399 -
400 - // Shells tighten on touch: outer margin is screen you don't get.
401 - (Self::Pane, Density::Pointer) => Step::Broad,
402 - (Self::Pane, Density::Touch) => Step::Loose,
403 -
404 - (Self::Page, Density::Pointer) => Step::Vast,
405 - (Self::Page, Density::Touch) => Step::Broad,
403 + Self::Peer => Step::Snug,
404 + Self::Group => Step::Roomy,
405 + Self::Section => Step::Wide,
406 + Self::Pane => Step::Broad,
407 + Self::Page => Step::Vast,
406 408 }
407 409 }
408 410
@@ -526,7 +528,12 @@
526 528 #[must_use]
527 529 pub fn density_css(explicit_touch: Option<&str>) -> String {
528 530 let mut css = geometry_css_vars(Density::Pointer);
529 - css.push_str("\n@media (hover: none), (pointer: coarse) {\n");
531 + // The block is emitted even though Touch currently resolves to Pointer's
532 + // steps, so it is a no-op repeat of the base layer. Deliberate: it keeps the
533 + // selection mechanism proven and in front of a reader, and a re-derived
534 + // Touch preset then reaches every consumer on a rebuild.
535 + css.push_str("\n/* Touch has no preset of its own yet: identical to the base layer. */\n");
536 + css.push_str("@media (hover: none), (pointer: coarse) {\n");
530 537 for line in gap_css_overrides(":root", Density::Touch).lines() {
531 538 css.push_str(" ");
532 539 css.push_str(line);
@@ -639,25 +646,22 @@
639 646 }
640 647
641 648 #[test]
642 - fn touch_separates_targets_and_tightens_shells() {
643 - // The asymmetry is the whole reason a base scalar would not do.
644 - for gap in [Gap::Peer, Gap::Section] {
645 - assert!(
646 - gap.px_at(Density::Touch) > gap.px_at(Density::Pointer),
647 - "{gap:?} must open up for a fingertip"
649 + fn touch_has_no_preset_of_its_own() {
650 + // Replaces touch_separates_targets_and_tightens_shells, which asserted
651 + // the derived preset thrown out on 2026-07-29: Peer and Section opened
652 + // while Pane and Page tightened. That put Touch's Section at 16 and made
653 + // any Pointer Pane at or below 16 an inversion, blocking the retune to
654 + // pane 14 / page 16.
655 + //
656 + // Asserted rather than left implicit so re-deriving Touch has to come
657 + // here and say so, the same way the theme degeneracy lists work.
658 + for gap in Gap::all() {
659 + assert_eq!(
660 + gap.px_at(Density::Touch),
661 + gap.px_at(Density::Pointer),
662 + "{gap:?}: Touch resolves to Pointer until a preset is derived"
648 663 );
649 664 }
650 - for gap in [Gap::Pane, Gap::Page] {
651 - assert!(
652 - gap.px_at(Density::Touch) < gap.px_at(Density::Pointer),
653 - "{gap:?} must tighten on a small screen"
654 - );
655 - }
656 - assert_eq!(
657 - Gap::Bound.px_at(Density::Touch),
658 - Gap::Bound.px_at(Density::Pointer),
659 - "bound things stay bound"
660 - );
661 665 }
662 666
663 667 #[test]
@@ -779,7 +783,9 @@
779 783 #[test]
780 784 fn a_density_override_emits_only_the_relational_layer() {
781 785 let css = gap_css_overrides(".ui-mode-mobile", Density::Touch);
782 - assert!(css.contains("--gap-peer: var(--step-roomy);"));
786 + // Whichever step it is: Touch has no preset of its own at the moment, so
787 + // this says the gap is emitted and references a step, not which one.
788 + assert!(css.contains("--gap-peer: var(--step-"));
783 789 // Referencing a step is the point; re-declaring one would fork the
784 790 // scale, so the check is on declarations, not on mentions.
785 791 let declared: Vec<&str> = css