Skip to main content

max / makeover-webview

0.32.0: name the one inline control, a link The vocabulary had no word for text that goes somewhere. A table cell has carried `cell-link` since 0.14.0's part list, deliberately unruled because the cell's own rule covers it, and outside a table there was nothing -- which went unnoticed while every consumer was a hand-written template that styled its own anchors. A description layer is what found it. quasi's containment model turns a cell into a run of leaves, so "this text is a link" has to be a member the run can hold rather than a field on the one container that had it; the renderer then needs a name for that member, and inventing one locally is the defect `cell-activate` already was -- a class with no rule behind it. Colour and underline, the hover step every other control takes, and the focus ring. Where a link sits and how much room it takes are the app's layout and makeover-geometry's spacing, so neither is here. The button arm is not decoration: a renderer picks the element from the method, so a link that writes is a `<button>` that has to stop looking like one. No visited arm. A link inside an app points at the app's own screens, which the user is expected to have been to, so painting them differently marks almost everything and distinguishes nothing.
Co-Authored-By
Claude Opus 5 (1M context) <noreply@anthropic.com>
Author: Max Johnson <me@maxj.phd> · 2026-08-12 15:18 UTC
Signed with PGP, not checked
Commit: d31d86b1e25015f0e4e397f575b685ce8d6a2ba7
Parent: 902b034
2 files changed, +56 insertions, -1 deletion
M Cargo.toml +1 -1
@@ -1,6 +1,6 @@
1 1 [package]
2 2 name = "makeover-webview"
3 - version = "0.31.0"
3 + version = "0.32.0"
4 4 edition = "2024"
5 5 # One copy of this renderer per dependency graph, enforced by cargo rather than
6 6 # by remembering. Two versions means the generated stylesheet and the emitted
M src/lib.rs +55
@@ -702,6 +702,55 @@
702 702 /// What a badge does carry is a [`Tone`], the intent family it shares with
703 703 /// notices and nothing else. Neutral is the bare class rather than a variant,
704 704 /// because it is the absence of a status and not a status called "none".
705 + /// Text that goes somewhere.
706 + ///
707 + /// The one inline control, and the vocabulary had no word for it until a
708 + /// description layer needed one. A table cell has carried `cell-link` since
709 + /// 0.14.0's part list, deliberately unruled because the cell's own rule covers
710 + /// it; what was missing is the same thing outside a table, which is what a
711 + /// described run holds when a sentence contains a link.
712 + ///
713 + /// Colour and underline only. Whether a link is inline in a sentence or sitting
714 + /// on its own line is the app's layout, and how much room it takes is
715 + /// `makeover-geometry`'s. What is here is the pair of signals that say "this
716 + /// goes somewhere" and nothing that says where it sits.
717 + ///
718 + /// The visited arm is deliberately absent. A link inside an app points at the
719 + /// app's own screens, which the user is expected to have been to, so painting
720 + /// them differently marks almost everything and distinguishes nothing.
721 + fn link_rules(opts: &Emit) -> String {
722 + let mut css = String::new();
723 + let link = class("link", opts);
724 +
725 + let _ = writeln!(
726 + css,
727 + ".{link} {{\n color: var(--action-primary);\n \
728 + text-decoration: underline;\n}}"
729 + );
730 + // The hover step is the same one every other control takes, and it is a
731 + // colour rather than a surface: a link has no box to raise.
732 + let _ = writeln!(
733 + css,
734 + "@media (hover: hover) and (pointer: fine) {{\n .{link}:hover \
735 + {{\n color: var(--action-hover);\n }}\n}}"
736 + );
737 + let _ = writeln!(
738 + css,
739 + ".{link}:focus-visible {{\n outline: {} solid var(--focus-ring);\n \
740 + outline-offset: 2px;\n}}",
741 + opts.focus_width
742 + );
743 + // A link is often a `<button>` rather than an `<a>`: a renderer picks the
744 + // element from the method, so a link that writes is a button that has to
745 + // stop looking like one.
746 + let _ = writeln!(
747 + css,
748 + "button.{link} {{\n background: none;\n border: none;\n \
749 + padding: 0;\n font: inherit;\n cursor: pointer;\n}}"
750 + );
751 + css
752 + }
753 +
705 754 fn token_rules(opts: &Emit) -> String {
706 755 let mut css = String::new();
707 756
@@ -1146,6 +1195,7 @@
1146 1195 pub fn component_rules(opts: &Emit) -> String {
1147 1196 let mut css = String::new();
1148 1197 css.push_str(&surface_rules(opts));
1198 + css.push_str(&link_rules(opts));
1149 1199 css.push_str(&token_rules(opts));
1150 1200 css.push_str(&selector_rules(opts));
1151 1201 css.push_str(&row_rules(opts));
@@ -1974,6 +2024,11 @@
1974 2024 // not magnitude and not colour.
1975 2025 | "hidden"
1976 2026 | "visible"
2027 + // The link's two signals. `underline` is a line and
2028 + // `inherit` defers to whatever the app set, so
2029 + // neither names a colour or a magnitude.
2030 + | "underline"
2031 + | "inherit"
1977 2032 // The table frame. `display` is structure and not a
1978 2033 // size; `nowrap` is what makes a content column
1979 2034 // content. The two widths are the awkward pair and