crates/gunnar-ui-web/src/narrow.rs
//! **On a phone the README opens in its plain grade** (GUN-FIELD-TEST, 2026-11-01), on the
//! host side of the wasm boundary. The typeset page is a drawing about 720 units wide;
//! at 400 px the console shrank it to text nobody could read. Below [`NARROW_PX`] the
//! README panel and a Markdown file's rendered view ask the server's plain grade
//! (`?as=html`) first; the HTML toggle still swaps to the typeset page.
/// Below this many CSS pixels of viewport, the plain grade comes first.
pub const NARROW_PX: f64 = 640.0;
/// Pure: does a viewport this wide open the plain grade first? An unknown width
/// (0) keeps the typeset default.
pub fn plain_first(viewport_width: f64) -> bool {
viewport_width > 0.0 && viewport_width < NARROW_PX
}
#[cfg(test)]
mod tests {
use super::*;
/// ★ A phone reads the plain grade; a desktop the typeset page.
#[test]
fn a_phone_opens_the_plain_grade_and_a_desktop_the_typeset_page() {
assert!(plain_first(400.0), "400 px");
assert!(plain_first(639.0));
assert!(!plain_first(640.0));
assert!(!plain_first(1280.0), "1280 px");
assert!(!plain_first(0.0), "an unknown width keeps the default");
}
/// The body of the first CSS rule for exactly `selector`.
fn rule<'a>(css: &'a str, selector: &str) -> &'a str {
css.split(&format!("\n{selector} {{")).nth(1).and_then(|r| r.split('}').next()).unwrap_or("")
}
/// ★ **No sideways page at 400 px** (GUN-FIELD-TEST, 2026-11-01). Measured on /repos and
/// /access: the document was 648 and 578 px wide. A table's screen-reader caption
/// (`.vh`, `position: absolute`) found no positioned ancestor and was placed in the
/// initial containing block at the wide table's edge, so the whole page scrolled
/// although the table scrolled in its own wrapper; and the page header's buttons did
/// not wrap. The wrapper contains its absolute children and the header wraps.
#[test]
fn a_wide_table_and_the_header_buttons_stay_inside_a_phone() {
let css = include_str!("../styles/console.css");
assert!(rule(css, ".table-wrap").contains("position: relative"), "the table wrapper contains its caption: {:?}", rule(css, ".table-wrap"));
assert!(rule(css, ".page-header").contains("flex-wrap: wrap"), "the page header wraps: {:?}", rule(css, ".page-header"));
}
/// ★ **Every data table scrolls inside its own wrapper** (GUN-FIELD-TEST, 2026-11-01:
/// Administration → Storage at 400 px scrolled the whole page, 665 px, under two tables
/// drawn straight into the panel). A `<table` in a page sits in a `.table-wrap` (or is a
/// file's or a diff's, which carry their own scrolling wrapper).
#[test]
fn every_data_table_scrolls_inside_its_wrapper() {
let pages: [(&str, &str); 4] = [
("pages/admin_storage.rs", include_str!("pages/admin_storage.rs")),
("pages/usage.rs", include_str!("pages/usage.rs")),
("pages/subscription.rs", include_str!("pages/subscription.rs")),
("pages/access.rs", include_str!("pages/access.rs")),
];
let mut bare = Vec::new();
for (file, src) in pages {
let lines: Vec<&str> = src.lines().collect();
for (i, line) in lines.iter().enumerate() {
if line.contains("<table") && !lines[i.saturating_sub(3)..=i].iter().any(|l| l.contains("table-wrap")) {
bare.push(format!("{file}:{}", i + 1));
}
}
}
assert!(bare.is_empty(), "tables outside a .table-wrap: {bare:?}");
}
/// ★ Both places that draw a typeset document ask for the plain grade on a phone.
#[test]
fn the_readme_panel_and_the_file_view_follow_the_width() {
let readme = crate::code::of(include_str!("pages/readme.rs"));
assert!(readme.contains("crate::narrow::plain_first("), "the README panel");
let browse = crate::code::of(include_str!("pages/browse.rs"));
assert!(browse.contains("crate::narrow::plain_first(") && browse.contains("&as=html"), "a Markdown file's rendered view");
}
}