//! Every class this renderer is responsible for, as a set rather than one name //! at a time. //! //! The twin of [`makeover_webview::vocabulary`], one layer up, and it exists //! for the same reason: a checker cannot ask "is this rule styling something //! nothing emits" without the list, and this crate is one of the two places //! that knows it. //! //! # Why an app needs this at all //! //! Until the described screens shipped, an app could answer the question by //! reading its own markup: `makeover_build::check_vocabulary_use` took //! `index.html` and the scripts beside it and asked which generated classes //! appeared in none of them. goingson's swap (goingson@f4edefb) deleted all of //! it, and the call was left out of that app's `build.rs` rather than run //! against nothing --- an app cannot see its own document any more, because its //! document is Rust in a dependency. //! //! That dependency is this crate. So the question moves here rather than //! disappearing, which is what this module is. //! //! # The set is closed, and that is the useful part //! //! A description has no word for a class. Everything in the document comes //! from this crate or from makeover. //! //! So [`covers`] is not a lower bound on what an app's stylesheet may //! legitimately match. It is the whole of it, and a selector outside it is dead //! rather than merely unaccounted for. That is what makes a drift check on the //! stylesheet exact instead of advisory. //! //! # One family is open, and it is not ours //! //! `col-`, from [`makeover_webview::list::push_column_class`]: a table //! cell carries a class built from its column's name, which is app data. So the //! set is closed over *names this tree writes* and open over *columns an app //! declares*, and no list can hold the second half. //! //! That is why [`covers`] exists beside [`names`] and is the function to check //! with. An app comparing selectors against [`names`] alone would call every //! `col-` rule dead and delete the table styling. //! //! # What the two vocabularies hold //! //! ```text //! makeover_webview::vocabulary::names() 73 //! this crate's own names 59 //! makeover names this crate spells 21 (button, list, table, run, ...) //! open families 1 (col-*) //! ``` //! //! The 21 are not repeated in [`OWN`]: this crate spells them because it emits //! them, and makeover writes their rules. What [`OWN`] holds is what makeover //! has never heard of --- the frame, the chrome, the region kinds, the screen //! arrangements and measures, the field wrappers, the row and table //! affordances --- for which the app's own stylesheet is the only thing that //! can carry a rule. //! //! # Why this list is written down and makeover's is scraped //! //! makeover generates the stylesheet, so it can parse what it just wrote and //! have no second source to drift from. This crate generates no CSS: it emits //! markup, and there is nothing to read back. A written list is therefore the //! only available shape, and [`super::tests`] carries the guard that makes it //! honest --- a render of every node kind, every region kind, every notice and //! every row state, scraped, with every class asserted to be covered. That //! guard is not decoration: it found fourteen names a careful read of the //! emitters had missed, including the `col-` family above. use std::collections::BTreeSet; use makeover_webview::Emit; /// The classes this crate emits that makeover does not define. /// /// Sorted, and kept that way: this is read as a set, and a list somebody /// appends to is a list nobody can diff. /// /// Unprefixed. [`names`] applies the prefix, because a prefix is host /// configuration and this is the vocabulary. pub const OWN: &[&str] = &[ "act-submit", // The popover container an `Outcome::Anchored` lands in, emitted beside // every region, beside a named control and beside a screen's selection. // Not `row-menu`: both are menus and only one of them belongs to a row. "anchored", "ask", "ask-body", "ask-open", // A notice's two kinds. Not makeover's: it has `well` and `card` for // standing content, and no word for something the app said unprompted. "banner", // The region kinds, from `RegionKind`. An arrangement of regions is a // page-level fact, which is why makeover has no word for one. "band", "bespoke", "chip-remove", "chrome-band", "chrome-brand", "chrome-brand-mark", "chrome-disclose", "chrome-disclose-state", "chrome-nav", "chrome-panel", "chrome-place", "chrome-search", "clock", "field-consults", "field-suggests", "field-writes", // `figures`, the strip these sit in, is makeover-webview's name and is // emitted here rather than owned here. It named it from 0.59.0. "figure-act", "form", "frame", "frame-status", "frame-verbs", "group", "heading", // The screen arrangements, from `Arrangement`. Two, because our apps have // two; see `Webview::arrangement_class` for why a third waits for a need. "list-detail", "list-detail-tabbed", // How wide a screen runs, from `Measure`. makeover has no word for it // because the answer is a page-level arrangement rather than anything it // styles. "measure-contained", "measure-reading", "measure-wide", "modal", "notices", "pane", "region", "region-consults", "rest", "rest-next", "rest-page", "rest-page-here", "rest-pages", "rest-position", "rest-previous", "rich", // A list row and its two states. makeover styles the row's *parts* // (`row-primary`, `row-meta`) and not the row, which is this renderer's // arrangement of them. "row", "row-activate", // The outline: a row indented under another, a row with a disclosure, and // the chevron itself. `ccaa7e4b`. The indent is a `--row-depth` custom // property on the row and the rule that reads it is the app's, the way // `row-select`'s box is. "row-branch", "row-disclose", "row-nested", // A live selection, which is neither `row-current` nor `row-selected`. See // `Row::chosen`. `1894e95d`. "row-chosen", "row-current", "row-menu", "row-select", "row-selected", "selector", "sidebar", "sidebar-content", "table-row-chosen", "table-row-current", "table-row-menu", "table-row-selected", "table-disclose", "table-disclose-head", "table-select", "table-select-head", "table-sort", "tabgroup", "text", "toast", "widget", ]; /// The prefix-free stem of the one open family. See the module header. const COLUMN_STEM: &str = "col-"; /// Every fixed class a document served by this renderer can contain. /// /// [`OWN`] plus [`makeover_webview::vocabulary::names`], prefixed the way /// `opts` prefixes them. The union rather than either part: this crate emits /// both, and an app checking against one would call the other dead. /// /// A `MAKEOVER_UNLISTED` constant stood beside [`OWN`] until /// makeover-webview 0.59.0, holding the three classes that crate emitted and /// did not name. It names its own unruled half now, so the workaround is gone /// rather than kept as a habit. /// /// **Prefer [`covers`] for checking a name.** This set is the fixed half; a /// `col-` class is legitimate and is not in here, because it cannot be. /// /// State classes arrive through makeover's half (`chosen`, `latched`), which /// are deliberately unprefixed there: they qualify a prefixed component rather /// than standing on their own. #[must_use] pub fn names(opts: &Emit) -> BTreeSet { let mut all = makeover_webview::vocabulary::names(opts); all.extend(OWN.iter().map(|name| makeover_webview::class(name, opts))); all } /// Whether this class is one a document served by this renderer can contain. /// /// [`names`], plus the open `col-` family that no set can hold. This is /// the function an app's drift check should ask, and asking [`names`] instead /// is how a check concludes that every table rule in a stylesheet is dead. /// /// # What an app does with it /// /// Two questions, opposite directions: /// /// - **Which emitted classes does the stylesheet never style?** Vocabulary that /// is emitted and unstyled. Usually fine, occasionally a missing rule. /// - **Which of the stylesheet's selectors match nothing that can be emitted?** /// Dead CSS, and exactly dead rather than probably dead, because the set is /// closed. This is the direction `check_vocabulary_use` could not answer once /// the markup left the app, and it is how goingson finds out how much of a /// 9,830-line stylesheet to delete. #[must_use] pub fn covers(class: &str, opts: &Emit) -> bool { let Some(stem) = class.strip_prefix(opts.class_prefix) else { return false; }; // A column class is named for app data, so it is checked by shape. The stem // alone is not enough: a bare `col-` names no column, and makeover reduces // a column's name to identifier characters before it becomes one. if let Some(column) = stem.strip_prefix(COLUMN_STEM) { return !column.is_empty(); } names(opts).contains(class) }