Skip to main content

Crate makeover_webview

Crate makeover_webview 

Source
Expand description

The webview renderer for makeover_layout.

§The renderer that needs no palette

makeover-immediate and makeover-tui both take a Palette, because egui and a terminal need an actual colour before they can put anything on screen. A webview does not: var(--surface-raised) is the late binding, and the browser resolves it against whatever themes.js last wrote onto :root.

So this crate emits text naming intents, and never learns a colour. It is the deferral rule with no adapter in the way, and it is why the webview was always the wrong renderer to derive a vocabulary from: it can express anything, so it never pushes back.

§Phase A: the stylesheet

This module emits component CSS and no markup, deliberately. GoingsOn has 145 innerHTML sites and Balanced Breakfast 175 createElement sites, so moving markup is a migration where adopting a generated stylesheet is not. The apps keep every line of their markup and gain the classes.

It is not a deletion either, which this header claimed until the measurement came in. Adoption across goingson removed 49 declarations net and added 25 lines: a rule loses its depth declarations and gains a variant selector next to it, so the file stays the same size. What phase A moves is where depth is defined, not how much CSS exists. Numbers and method in the wiki note under “The deletion test, run”.

The bevel properties are byte-identical to what both apps already hand-write, which is asserted below.

§Phase B: the markup, one description at a time

form renders makeover_layout::Field, which is the half of phase B whose description is settled. It emits strings, because both apps interpolate their fields into larger string-built forms and returning nodes would rewrite those too. It owns its own escaping, on the reasoning in that module: a Rust encoder can cover element text and attribute values with one function, where the apps need four and have to choose correctly at every call site.

list is the other half: column tracks, the narrowing rules, and the cell containers a row is made of. It stops at the cell boundary and does not render what goes inside one, on the reasoning in that module. So phase B is now the frame around content in both directions, and what an app still owns is the content itself.

§What phase A settled, and what it costs

Decided 2026-07-29 against goingson’s styles.css rather than against a component list. The useful finding there was that .btn (line 644), .card (768) and .tag, .badge (882) each hand-write the same composition, so three quarters of phase A is one rule with several names.

Two of the four decisions change how goingson looks, and adoption should not be described as a pure deletion:

  • Pressed carries its fill. interactive_rules emits Depth::pressed whole. goingson presses to --surface-sunken today and will press to --surface-well, and hovers to --surface-overlay today and will hover to --hover-surface. Since surface-well inverts by theme where surface-sunken does not, a dark theme presses lighter than it hovers. That falls out of makeover’s own derivation, which says outright that surface-sunken cannot serve as a well, so if it reads wrong the answer is there and not here.
  • Badges go flat. See [token_rules].

The other two: the progress trough is renderer-local and the scrollbar track was dropped (component_rules), and no class prefix ships by default, so adoption means deleting the app’s hand-written rule in the same commit that adds the generated one. .card, .badge and the tab classes all already exist in goingson, and while both rules exist the cascade order decides which wins. That is the one real risk in adopting this, and it is why the migration lands per component rather than in one commit.

§0.10.0: the states this crate used to leave to its consumers

interactive_rules emitted hover and pressed and stopped, because makeover-layout modelled no interaction state. Focus and disabled were therefore unsayable, and every app completed the primitive from outside the only way that works: by out-specifying a rule it does not own. goingson carries 19 such rules and the MNW server 21, and the three focus rings do not match each other.

That also blocked the cascade-layer work outright. An app that declares @layer puts its own rules in a named layer, and unlayered declarations outrank every named layer regardless of specificity, so all of those overrides lose in the commit that adopts layers. They cannot simply be deleted, because they are the only thing supplying the missing states. Emitting the states here is what turns that adoption into a deletion.

Four states now, in emission order, and the order is load-bearing: they are all specificity (0,2,0), so disabled beats hover by coming last and by nothing else. Nothing here reaches for :not(:disabled), which would raise a selector this crate will shortly be wrapping in its own layer.

Hover additionally sits inside a capability query now. makeover-touch answers whether a fingertip has hover and makeover-geometry spells the condition; this crate asks and does not decide. goingson’s section 60 exists solely to take the hover state back on touch, which is a fight it should never have been handed.

§0.11.0: the layer contract

stylesheet emits into the makeover cascade layer (CSS_LAYER, which lives in makeover-geometry because that is the one crate every CSS emitter in the family already depends on). makeover-geometry 0.6.0 does the same for geometry.css.

The cascade resolves origin and importance, then layer, then specificity, then source order, and unlayered normal declarations outrank every named layer. So before this, an app that declared @layer base, components, responsive put every rule it owns into a named layer and lost all of them to this unlayered file, regardless of specificity and regardless of loading last. Nothing errors when that happens: the CSS is valid, the minifier is happy, and buttons and badges look subtly wrong.

That is why the layer belongs here rather than in each app. An app cannot fix it from its own stylesheet, because the fix is to layer the file it does not own.

What it flips, and the reason each app wants a look when it bumps the pin: a generated rule that currently beats an app rule by being more specific stops beating it. The direction is always “the app wins”, which is what the apps already assume, but a hand-written rule an app thought was dead can come back to life.

An app should declare the order once, or the layer’s position is decided by whichever generated file the browser happens to see first:

@layer makeover, base, components, responsive;

in_css_layer is re-exported for an app that assembles its own stylesheet from this crate’s pieces. goingson builds tables.css in its own build.rs out of list::narrowing_css and list::grid_template_columns, and those rules are as generated as the ones here, so they belong in the same layer and this crate cannot put them there on the app’s behalf.

§0.12.0: the ring gets its own width

focus_rule reused Emit::border_width and emitted a 1px ring. That was an implementation convenience dressed as consistency with the invalid-field ring: a bevel and a focus indicator answer different questions, and only one of them has to be noticed from across a desk.

Caught while adopting 0.11.0 into goingson, by the check the adoption tasks ask for. Every consumer had already written its own ring and all three chose at least 2px: the MNW server 2px across 10 rules, Balanced Breakfast 2px, goingson 2px on three rules and 3px on the one covering twelve selectors. The design system was the only thing in the tree saying 1px, so deleting the app rules in favour of it would have thinned the focus indicator everywhere.

Emit::focus_width now carries it, defaulting to 2px, and the offset is the same magnitude with its sign off the depth. Both values are the measured consensus rather than a new opinion.

§0.17.0: the depth classes stop being controls

depth_rules gave .raised the whole interactive set. A depth is a statement about shape, so that left the vocabulary with no raised surface that is merely an object, and an app wanting one had two moves: write its own class from tokens, or take a control class and cancel the control half. goingson took the second, in three variants over sixteen elements (.card--static at 14 call sites, .card--muted at 2, .card--shell at 1), each re-asserting the resting fill and bevel on :hover and :active.

Measured before changing it: .raised is emitted into goingson, Balanced Breakfast and the MNW server, and none of the three has a single call site. The states were unasked-for everywhere at once, and dropping them costs no migration anywhere.

.card and .button are unchanged. They are the same depth and controls, and they take their states from [surface_rules], which is where a state belongs: on the thing that claims to answer a pointer.

§Substitution, three ways

Fill::Well has no colour on makeover before 2.3.0, and each renderer answers that differently, which is the evidence that dropping Fill::fallback from the description was right:

  • makeover-immediate substitutes the page in Rust.
  • makeover-tui refuses to substitute and draws an edge instead, because a terminal would quantise the two together.
  • here, CSS already has the mechanism: var(--surface-well, var(--surface-page)) falls back in the browser, and nothing in Rust decides anything.

Modules§

figure
A figure with a caption, and a strip of them.
form
Phase B, the forms half: makeover_layout::Field rendered to HTML.
list
Column layout and row structure for lists and tables.
meter
A proportion, rendered as a bar.
placeholder
What a region shows when it is not showing its content.

Structs§

Emit
How the emitted CSS is shaped.

Constants§

CSS_LAYER
The cascade layer every stylesheet the make-family generates is wrapped in.

Functions§

bevel_properties
The custom properties both bevels resolve through.
bevel_shadow
The two-tone edge as a box-shadow value.
bevel_var
The CSS custom property holding a bevel’s composition.
component_rules
The component layer: every named thing phase A emits.
depth_class
The class name for a depth.
depth_declarations
The fill and edge declarations for a depth, as a rule body.
depth_rule
One rule giving a selector a depth, or nothing when the depth declares nothing.
depth_rules
One rule per depth: its fill and its edge, together.
disabled_rule
Present, visible, and not answering.
fill_var
A var() reference to a fill intent, with the browser’s own fallback where the intent may be absent.
focus_rule
The keyboard focus ring, placed by the depth it lands on.
in_css_layer
Wrap generated CSS in CSS_LAYER.
interactive_rules
Every state a selector that answers a click implies: hover, pressed, focus and disabled, in that order.
stylesheet
The whole phase-A stylesheet: properties, depth rules and components, in CSS_LAYER, under a generated-file banner.