max / makeover-geometry
- Co-Authored-By
- Claude Opus 5 (1M context) <noreply@anthropic.com>
1 file changed,
+52 insertions,
-46 deletions
| @@ -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 |