crates/gunnar-ui-web/src/nav.rs
//! **Where everything lives**: the top bar, a repository's tabs, and the old
//! addresses that now lead somewhere else — on the host side of the wasm
//! boundary, so a test can state the menu.
//!
//! The owner's final navigation (2026-10-02):
//!
//! ```text
//! [gunnar] Repositories (search…) You ▾ Administration ▾
//! ```
//!
//! * **Repositories** is the only main entry and everybody's landing page.
//! * **You ▾**: Account; Activity (the events of the reader's OWN account,
//! filtered on the server); Keys & passkeys; Log out. No Storage entry
//! (owner 2026-10-17): buying is the ONE sign-up page, from Account's button.
//! * **Administration ▾**, an administrator's only: Overview (the whole
//! server's events, and the Machine card: the estate's boxes and the few
//! lines gunnar judges the disks by), Storage (the machine's: data set,
//! engine, layout, disks, twin, /serialdat, performance — every disk detail,
//! once), Identities, Mirrors, Instruments. No Machine page any more (owner,
//! 2026-10-04): it said again what Overview and Storage say.
//! * **Inside a repository**: Code · History · Pulls · Issues · Activity ·
//! Settings.
//! * Every old address (`/`, `/overview`, `/events`, `/storage`,
//! `/usage[/…]`, `/access`, `/machine`) leads to its new home ([`moved`]);
//! `/machine` to the Overview, where its card is.
/// One menu entry.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Link {
/// The menu it sits in.
pub group: &'static str,
pub label: &'static str,
pub href: &'static str,
/// Drawn as a sub-entry of the link before it.
pub sub: bool,
}
/// Who is reading, as far as the top bar is concerned.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub struct Reader {
pub signed_in: bool,
pub may_write: bool,
pub is_admin: bool,
/// **A member of an account** — gunnar's word (`/auth/status`
/// `account_role: employee`, `Status::is_member`): an enterprise account's
/// person, whom gunnar keeps out of every clan (round 5.3).
pub member: bool,
}
/// gunnar's word (`/auth/status` `account_role`) for a member the account's admin gave Admin
/// over the account (owner 2026-10-08, round 5.3.1).
pub const APPOINTED_ADMIN: &str = "admin-appointed";
/// **A member of an account** — uses its space, buys nothing: an employee, and an employee
/// appointed its admin.
pub fn is_member_role(account_role: Option<&str>) -> bool {
matches!(account_role, Some("employee" | APPOINTED_ADMIN))
}
/// **Is the account's Audit log offered?** gunnar admits the account's admins alone (and
/// the operator's own sessions, which name no account): never a plain member.
pub fn shows_audit_log(account_role: Option<&str>) -> bool {
!is_member_role(account_role) || account_role == Some(APPOINTED_ADMIN)
}
/// **The top bar's menus.**
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct Menu {
/// Left: the main entries.
pub main: Vec<Link>,
/// "You ▾": the reader's own.
pub you: Vec<Link>,
/// "Administration ▾": an administrator's only.
pub admin: Vec<Link>,
}
/// **The top bar** (owner's final navigation, 2026-10-02):
/// `[gunnar] Repositories (search…) You ▾ Administration ▾`.
///
/// * Repositories — the only main entry, and every reader's;
/// * You ▾ — a signed-in reader's own: their Account, its Activity (the
/// events of their own account), their keys and passkeys — and Log out, which is the
/// menu's own button. Room is left for "Members" (the account model);
/// * Administration ▾ — an ADMINISTRATOR's only: the Overview (the whole
/// server's events and counts, and the Machine card), the machine's Storage,
/// Identities, Mirrors, and the Instruments page (passkeys, replication,
/// orphan stores, the console's own identity). Overview · Storage ·
/// Identities · Mirrors · Instruments (owner, 2026-10-04); since 2026-10-05
/// the Identity provider (company SSO) after Identities — an account admin
/// who is no box admin reaches it from their account's page — and (the
/// owner's enterprise list) Accounts — the accounts' totals — after
/// Overview, and Audit log before Instruments.
pub fn menu(r: Reader) -> Menu {
let link = |group, label, href| Link { group, label, href, sub: false };
let mut m = Menu { main: vec![link("Main", "Repositories", "/repos")], ..Menu::default() };
if !r.signed_in {
return m;
}
m.you = vec![
link("You", "Account", "/account"),
link("You", "Activity", "/account/activity"),
// Clans (owner, 2026-10-05): the accounts the reader groups with.
link("You", "Groups", "/account/clans"),
link("You", "Keys & passkeys", "/account#keys"),
];
// **Never a member's** (round 5.3; the members walkthrough of 2026-10-07):
// gunnar keeps an enterprise account's people out of every clan ("ben is
// an employee of acme: an enterprise account's people may not make a clan
// — their account admin's rules win"), so the entry led a member to a
// form gunnar refuses. gunnar's word on who is a member decides; nothing
// is decided here but not drawing a door it closed.
if r.member {
m.you.retain(|l| l.href != "/account/clans");
}
if r.is_admin {
m.admin = vec![
link("Administration", "Overview", "/admin/overview"),
// The accounts' TOTALS (owner's enterprise list, 2026-10-05): the
// operator sees no account's names or events.
link("Administration", "Accounts", "/admin/accounts"),
link("Administration", "Storage", "/admin/storage"),
link("Administration", "Identities", "/access"),
link("Administration", "Identity provider", "/admin/identity-provider"),
link("Administration", "Mirrors", "/mirrors"),
// The account admin's audit log — of the administrator's OWN
// account (another account's admin reaches theirs from Account).
link("Administration", "Audit log", "/admin/audit"),
link("Administration", "Instruments", "/admin"),
];
}
m
}
/// **The role badge on the Account page**, or none: an operator's and an
/// administrator's role is shown; a viewer — every ordinary person — sees no
/// badge (owner order 2026-10-18).
pub fn role_badge(role: Option<&str>) -> Option<String> {
role.filter(|r| matches!(*r, "operator" | "admin")).map(str::to_owned)
}
/// The Provenance tab (the signed chain of a line): hidden from the tabs
/// since 2026-10-18 (owner, wide audience). The page itself still answers.
pub const SHOW_PROVENANCE: bool = false;
/// **A repository's tabs**: Code · History · Pulls · Issues · Activity ·
/// Settings. Pulls and Activity need a session (their reads are
/// behind one), Issues are a visitor's too (GUN-GUEST); Settings is for whoever may change the repository (an
/// operator or an administrator — the server refuses anybody else whatever
/// is drawn).
/// **What a guest on a repository's Settings sees** (GUN-FIELD-TEST, 2026-11-01): only this,
/// and a Log in link; no settings, no Danger zone.
pub const SETTINGS_SIGN_IN: &str = "Sign in to see this repository's settings.";
/// **Which tab is the current one** (GUN-FIELD-TEST, 2026-11-01): the tab whose address
/// is the longest prefix of `here`; the README's (the repository's own address) only on
/// that address exactly. A page without a tab of its own (a guest on Pulls or Settings)
/// marks none: it marked Readme, the prefix of everything.
pub fn current_tab(tabs: &[(&'static str, String)], here: &str) -> Option<&'static str> {
let home = tabs.first().map(|(_, h)| h.as_str()).unwrap_or("");
tabs.iter()
.filter(|(_, href)| here == *href || (href.as_str() != home && here.starts_with(&format!("{href}/"))))
.max_by_key(|(_, href)| href.len())
.map(|(label, _)| *label)
}
pub fn repo_tabs(namespace: &str, name: &str, signed_in: bool, may_write: bool) -> Vec<(&'static str, String)> {
// GUN-GUEST (owner 2026-10-30): the one address scheme, `/{ns}/{name}/…` (`crate::addr`).
let base = crate::addr::repo(namespace, name);
// One row for the whole repository (owner, 2026-10-02): what used to be
// the Code tab's header buttons are tabs too, and the README is the home.
let mut tabs = vec![
("Readme", base.clone()),
("Code", format!("{base}/tree")),
("History", format!("{base}/commits")),
("Compare", format!("{base}/diff")),
("Search", format!("{base}/search")),
("Blame", format!("{base}/blame")),
];
// HIDDEN (owner order 2026-10-18, wide audience): the Provenance tab.
// Its page stays at its address, behind this flag.
if SHOW_PROVENANCE {
tabs.push(("Provenance", format!("{base}/chain")));
}
if signed_in {
tabs.push(("Pulls", format!("{base}/pulls")));
}
// A visitor reads a public repository's issues too (GUN-GUEST); filing and
// commenting ask them to sign in (`crate::guest`).
tabs.push(("Issues", format!("{base}/issues")));
if signed_in {
tabs.push(("Activity", format!("{base}/activity")));
}
if may_write {
tabs.push(("Settings", format!("{base}/settings")));
}
tabs
}
/// **Where an old address leads now**, or `None` to draw it where it is.
///
/// Not a permission: the server refuses every one of these reads to whoever
/// may not make them. This only stops a person landing on a page that would
/// draw refusals, and keeps every old link — a bookmark, a buy link —
/// working.
pub fn moved(path: &str, is_admin: bool) -> Option<&'static str> {
let path = path.trim_end_matches('/');
let path = if path.is_empty() { "/" } else { path };
let usage = path == "/usage" || path.starts_with("/usage/");
match (path, is_admin) {
// Repositories is the default, for everybody.
("/", _) => Some("/repos"),
// The Machine page is gone (owner, 2026-10-04): its card is on the
// Overview, its disk details on Administration → Storage.
("/overview", true) | ("/events", true) | ("/machine", true) => Some("/admin/overview"),
("/overview", false) | ("/admin/overview", false) | ("/machine", false) => Some("/repos"),
("/events", false) => Some("/account/activity"),
("/storage", true) => Some("/admin/storage"),
("/storage", false) | ("/admin/storage", false) => Some("/account"),
("/access", false) => Some("/account"),
_ if usage && !is_admin => Some("/account"),
_ => None,
}
}
#[cfg(test)]
mod tests {
use super::*;
fn labels(links: &[Link]) -> Vec<&'static str> {
links.iter().map(|l| l.label).collect()
}
const VIEWER: Reader = Reader { signed_in: true, may_write: false, is_admin: false, member: false };
const ADMIN: Reader = Reader { signed_in: true, may_write: true, is_admin: true, member: false };
const OPERATOR: Reader = Reader { signed_in: true, may_write: true, is_admin: false, member: false };
/// An account's member, as gunnar names one (`account_role: employee`).
const MEMBER: Reader = Reader { signed_in: true, may_write: false, is_admin: false, member: true };
/// ★ **No "New instance" in the top bar** (owner 2026-10-27, Gunnar is the template; reverts
/// ALICA-1's top-bar door, ce76319): a private instance is ordered where it always was,
/// Account → "Buy a plan", whose page has the two tabs "New instance | Buy a plan".
#[test]
fn the_top_bar_has_no_new_instance() {
for r in [VIEWER, ADMIN, OPERATOR, MEMBER, Reader::default()] {
let m = menu(r);
for l in m.main.iter().chain(&m.you).chain(&m.admin) {
assert!(!l.label.to_lowercase().contains("instance"), "{r:?}: the top bar offers {}", l.label);
}
}
let app = include_str!("app.rs");
assert!(!app.contains("new-instance") && !app.contains("New instance") && !app.contains("new_instance"), "the top bar draws a New instance");
}
/// ★ **An appointed admin is a member who sees the audit log** (round 5.3.1): he buys
/// nothing like every member, and gunnar admits him to the account's audit log.
#[test]
fn an_appointed_admin_is_a_member_who_sees_the_audit_log() {
assert!(is_member_role(Some("employee")) && is_member_role(Some(APPOINTED_ADMIN)));
for role in [Some("admin"), Some("individual"), None] {
assert!(!is_member_role(role), "{role:?}");
assert!(shows_audit_log(role), "{role:?}");
}
assert!(shows_audit_log(Some(APPOINTED_ADMIN)), "the appointed admin reads the audit log");
assert!(!shows_audit_log(Some("employee")), "a member is not shown the door");
}
/// ★ **A member's You ▾ has no Clans** (round 5.3; the members walkthrough
/// of 2026-10-07: ben's You ▾ offered Clans and its "Make a clan" form,
/// which gunnar refuses an enterprise account's people). Everything else
/// in it is his own and stays; an account's admin and an individual keep
/// Clans.
#[test]
fn a_members_you_menu_has_no_clans() {
assert_eq!(labels(&menu(MEMBER).you), ["Account", "Activity", "Keys & passkeys"]);
assert!(menu(MEMBER).admin.is_empty(), "a member is no administrator");
for r in [VIEWER, OPERATOR, ADMIN] {
assert!(labels(&menu(r).you).contains(&"Groups"), "not a member keeps Groups: {r:?}");
}
}
/// ★ **The final navigation** (owner, 2026-10-02): a top bar —
/// `[gunnar] Repositories (search…) You ▾ Administration ▾` —
/// Repositories the only main entry; You ▾ a person's own (Account, its
/// Activity, keys and passkeys; log out is the menu's own
/// button); Administration ▾ ONLY for an administrator. No top-level
/// Storage, Usage, Overview or Events any more.
#[test]
fn the_top_bar_has_repositories_you_and_administration_only_for_an_admin() {
for r in [Reader::default(), VIEWER, OPERATOR, ADMIN] {
assert_eq!(labels(&menu(r).main), ["Repositories"], "{r:?}");
assert_eq!(menu(r).main[0].href, "/repos");
}
assert!(menu(Reader::default()).you.is_empty(), "a visitor has no You menu (the top bar's Log in instead)");
let you = ["Account", "Activity", "Groups", "Keys & passkeys"];
for r in [VIEWER, OPERATOR, ADMIN] {
assert_eq!(labels(&menu(r).you), you, "{r:?}");
}
for r in [Reader::default(), VIEWER, OPERATOR] {
assert!(menu(r).admin.is_empty(), "Administration is shown to an administrator only: {r:?}");
}
assert_eq!(labels(&menu(ADMIN).admin), ["Overview", "Accounts", "Storage", "Identities", "Identity provider", "Mirrors", "Audit log", "Instruments"]);
let href = |links: &[Link], label: &str| links.iter().find(|l| l.label == label).unwrap().href;
assert_eq!(href(&menu(ADMIN).admin, "Identity provider"), "/admin/identity-provider");
assert_eq!(href(&menu(VIEWER).you, "Activity"), "/account/activity");
assert_eq!(href(&menu(VIEWER).you, "Groups"), "/account/clans", "a person's clans are theirs (You ▾)");
assert_eq!(href(&menu(ADMIN).admin, "Overview"), "/admin/overview");
assert_eq!(href(&menu(ADMIN).admin, "Storage"), "/admin/storage");
assert_eq!(href(&menu(ADMIN).admin, "Accounts"), "/admin/accounts");
assert_eq!(href(&menu(ADMIN).admin, "Audit log"), "/admin/audit");
for r in [Reader::default(), VIEWER, OPERATOR, ADMIN] {
let m = menu(r);
let all: Vec<&str> = m.main.iter().chain(&m.you).chain(&m.admin).map(|l| l.href).collect();
for gone in ["/storage", "/usage", "/events", "/", "/overview", "/machine"] {
assert!(!all.contains(&gone), "{gone} is still a menu entry: {all:?}");
}
}
}
/// ★ **Repositories is the default, and every old address leads to its
/// new home** — a person's or an administrator's — so no link breaks.
#[test]
fn every_old_address_leads_to_its_new_home() {
for admin in [false, true] {
assert_eq!(moved("/", admin), Some("/repos"), "everybody lands on Repositories");
}
assert_eq!(moved("/overview", false), Some("/repos"));
assert_eq!(moved("/overview", true), Some("/admin/overview"));
assert_eq!(moved("/admin/overview", false), Some("/repos"), "the Overview is not a person's");
assert_eq!(moved("/admin/overview", true), None);
assert_eq!(moved("/events", false), Some("/account/activity"), "a person's events are their account's");
assert_eq!(moved("/events", true), Some("/admin/overview"), "the whole server's events are the Overview's");
assert_eq!(moved("/storage", false), Some("/account"));
assert_eq!(moved("/storage", true), Some("/admin/storage"));
assert_eq!(moved("/admin/storage", false), Some("/account"));
assert_eq!(moved("/usage", false), Some("/account"));
assert_eq!(moved("/usage/anna", false), Some("/account"));
assert_eq!(moved("/usage", true), None);
assert_eq!(moved("/usage/anna", true), None);
assert_eq!(moved("/access", false), Some("/account"), "a person's keys are under Account");
assert_eq!(moved("/access", true), None);
assert_eq!(moved("/machine", false), Some("/repos"));
assert_eq!(moved("/machine", true), Some("/admin/overview"), "the Machine page is the Overview's card now");
// The Identity provider (company SSO) and the Audit log (2026-10-05)
// are the ACCOUNT admin's: they stay for a reader who is no box admin,
// who reaches them from Account.
for stays in ["/account", "/account/storage", "/account/activity", "/repos", "/a/b", "/mirrors", "/admin/identity-provider", "/admin/audit"] {
assert_eq!(moved(stays, false), None, "{stays}");
}
}
#[test]
fn a_viewer_is_shown_no_role_badge() {
assert_eq!(role_badge(Some("viewer")), None);
assert_eq!(role_badge(None), None);
assert_eq!(role_badge(Some("operator")).as_deref(), Some("operator"));
assert_eq!(role_badge(Some("admin")).as_deref(), Some("admin"));
}
/// ★ **Inside a repository: Readme · Code · History · Compare · Search ·
/// Blame · Provenance · Pulls · Issues · Activity · Settings** — the collaboration tabs for a signed-in session (their
/// APIs are behind one), Settings for whoever may change the repository.
#[test]
fn a_repository_has_its_tabs_and_settings_only_for_who_may_change_it() {
let tabs = |signed_in, may_write| repo_tabs("alice", "uitest", signed_in, may_write).into_iter().map(|(l, _)| l).collect::<Vec<_>>();
// GUN-GUEST (owner 2026-10-30): a visitor reads a public repository's issues too.
assert_eq!(tabs(false, false), ["Readme", "Code", "History", "Compare", "Search", "Blame", "Issues"]);
assert_eq!(tabs(true, false), ["Readme", "Code", "History", "Compare", "Search", "Blame", "Pulls", "Issues", "Activity"]);
assert_eq!(tabs(true, true), ["Readme", "Code", "History", "Compare", "Search", "Blame", "Pulls", "Issues", "Activity", "Settings"]);
let hrefs: Vec<String> = repo_tabs("alice", "uitest", true, true).into_iter().map(|(_, h)| h).collect();
assert_eq!(
hrefs,
["/alice/uitest", "/alice/uitest/tree", "/alice/uitest/commits", "/alice/uitest/diff", "/alice/uitest/search", "/alice/uitest/blame", "/alice/uitest/pulls", "/alice/uitest/issues", "/alice/uitest/activity", "/alice/uitest/settings"]
);
assert_eq!(repo_tabs("a b", "c", false, false)[0].1, "/a%20b/c", "names are path segments");
}
/// ★ **Your mirrors live with the repositories, not in a menu** (owner,
/// 2026-10-02, fitting tenant-import into the final navigation): an
/// "Imported only" filter on Repositories with a link to the reader's
/// mirrors, the same link under a repository's "Mirrored from" panel, the
/// "Import repository" button beside "New repository" — and the box-wide
/// Mirrors page under Administration only.
#[test]
fn your_mirrors_are_found_from_the_repositories_not_from_a_menu() {
for r in [VIEWER, OPERATOR, ADMIN] {
let m = menu(r);
assert!(!m.you.iter().chain(&m.main).any(|l| l.href == "/mirrors"), "{r:?}: a top-level mirrors entry");
}
assert!(menu(ADMIN).admin.iter().any(|l| l.label == "Mirrors"));
let repos = page("repos.rs");
// "Imported only" is gunnar's `mirrored_only`, asked of the server
// (thin layer, 2026-10-04) — not a set the page builds itself.
assert!(repos.contains("Imported only") && repos.contains("imported=true") && repos.contains("href=\"/mirrors\""), "no Imported filter / Your mirrors link on Repositories");
let (new, import) = (repos.find("\"New repository\"").unwrap(), repos.find("\"Import repository\"").unwrap());
assert!(new < import, "Import repository sits beside New repository");
let mirrors = page("mirrors.rs");
let panel = &mirrors[mirrors.find("pub fn MirrorPanel").unwrap()..];
assert!(panel[..panel.find("\n}\n").unwrap()].contains("href=\"/mirrors\""), "the repository's Mirrored from panel does not lead to Your mirrors");
}
fn page(name: &str) -> String {
std::fs::read_to_string(std::path::Path::new(env!("CARGO_MANIFEST_DIR")).join("src/pages").join(name)).unwrap()
}
/// The panels of a page source, in the order they are drawn.
fn panels(src: &str) -> Vec<String> {
src.match_indices("<Panel title=\"").map(|(i, m)| src[i + m.len()..].split('"').next().unwrap().to_owned()).collect()
}
/// ★ **`/account/storage` is no purchase page** (owner 2026-10-17): it
/// goes on to the ONE sign-up page's door, or to Account — no offers, no
/// panel of its own; and the room it reads stays the session's own, and
/// nothing of the machine's.
#[test]
fn account_storage_goes_on_and_draws_no_offers() {
let src = page("my_storage.rs");
assert!(src.contains("crate::purchase::storage_goes_to(s.buy_url)"), "the route does not go on to the door");
for gone in ["MoreRoom", "SignUpDoors", "signup-grow", "signup-private", "Buy a private Gunnar instance", "subscription::"] {
assert!(!src.contains(gone), "{gone} is still on Storage");
}
// The read-only view of what is used stays, on Account (owner 2026-10-17).
let own = src.find("pub fn OwnSpace").expect("the person's own room");
assert!(src[own..].contains("<MyRoom") && src[own..].contains("<PerRepository"), "My room and Per repository");
assert!(src.contains("/api/account/room"), "a member's room reads the session's own namespace");
for machine in ["/api/disks", "/api/dataset", "/api/storage\"", "/api/machine", "DataSetPanel", "TwinPanel"] {
assert!(!src.contains(machine), "the person's room shows the machine's {machine}");
}
}
/// ★ **Administration → Storage**: the machine, in the owner's order, and
/// the Data set ONCE in the whole console.
#[test]
fn administration_storage_has_the_machine_in_order_and_the_data_set_once() {
let src = page("admin_storage.rs");
let order = ["Data set", "Engine", "Disks", "Twin", "/serialdat", "Performance"];
let at: Vec<usize> = order.iter().map(|p| src.find(&format!("<Panel title=\"{p}\"")).unwrap_or_else(|| panic!("no {p} panel"))).collect();
assert!(at.windows(2).all(|w| w[0] < w[1]), "{order:?} out of order");
assert!(src.contains("is_admin()"), "Administration → Storage is an administrator's");
// The engine: its NAME, linked to its source, its description the
// tooltip — never the prose line gunnar used to send.
assert!(src.contains("href=engine.repo_url") && src.contains("title=engine.description"), "the engine is not name + link + tooltip");
assert!(!src.contains("object_backend"), "the engine's prose line is still drawn");
let dir = std::path::Path::new(env!("CARGO_MANIFEST_DIR")).join("src/pages");
let mut data_sets = Vec::new();
for e in std::fs::read_dir(dir).unwrap().flatten() {
let s = std::fs::read_to_string(e.path()).unwrap();
for p in panels(&s) {
if p == "Data set" {
data_sets.push(e.file_name().to_string_lossy().into_owned());
}
}
assert!(!s.contains("<h3>\"Data set\"</h3>"), "a second Data set in {:?}", e.file_name());
}
assert_eq!(data_sets, ["admin_storage.rs"], "the Data set is shown once");
}
/// ★ UW-19: **a grant is revoked from its row** — the Access page's grants carry an
/// admin's Revoke, posted to `/api/grants/revoke` (the route had no caller).
#[test]
fn a_grant_is_revoked_from_its_row_on_the_access_page() {
let access = page("access.rs");
assert!(access.contains("\"/api/grants/revoke\""), "no Revoke on the grants list");
}
/// ★ **No Machine page** (owner, 2026-10-04): the Overview's Machine card
/// carries the estate's picture and gunnar's few lines; every disk detail
/// is on Administration → Storage, ONCE; and Instruments has no "Pages"
/// panel repeating the Administration menu.
#[test]
fn the_machine_is_a_card_on_the_overview_and_its_disks_are_on_storage_once() {
let app = std::fs::read_to_string(std::path::Path::new(env!("CARGO_MANIFEST_DIR")).join("src/app.rs")).unwrap();
assert!(!app.contains("pages::machine::Machine"), "the Machine page is still routed");
let overview = page("overview.rs");
assert!(overview.contains("<Panel title=\"Machine\""), "no Machine card on the Overview");
assert!(overview.contains("\"/api/machine/topology\"") && overview.contains("inner_html"), "the card does not draw the estate from Admin.Topology, inline");
assert!(overview.contains("glance_grade"), "the card does not draw gunnar's glance lines by gunnar's verdict");
let storage = page("admin_storage.rs");
for panel in ["machine::Layout", "machine::BoxAllocation"] {
assert!(storage.contains(panel), "Storage lacks {panel}");
}
let dir = std::path::Path::new(env!("CARGO_MANIFEST_DIR")).join("src/pages");
for e in std::fs::read_dir(dir).unwrap().flatten() {
let name = e.file_name().to_string_lossy().into_owned();
let s = std::fs::read_to_string(e.path()).unwrap();
if name != "admin_storage.rs" {
assert!(!s.contains("machine::Layout") && !s.contains("machine::BoxAllocation"), "{name} draws a disk detail Storage already draws");
}
}
let admin = page("admin.rs");
assert!(!admin.contains("<Panel title=\"Pages\"") && !admin.contains("admin-links"), "Instruments still repeats the menu");
}
/// ★ **No "N/A — not reported / not measured" rows**: a row is drawn only
/// when gunnar reports a value (write amplification, priority transfer,
/// the read cache's high/low water, its hit/miss counts, a cold tier an
/// archive engine does not have).
#[test]
fn no_page_draws_a_row_gunnar_did_not_report() {
let dir = std::path::Path::new(env!("CARGO_MANIFEST_DIR")).join("src/pages");
for e in std::fs::read_dir(dir).unwrap().flatten() {
let s = std::fs::read_to_string(e.path()).unwrap();
let name = e.file_name().to_string_lossy().into_owned();
for na in ["\"N/A\"", "na_line(", "hysteresis_line(", "Write amplification", "Priority transfer", "Hit and \\\n", "miss counts", "not reported by gunnar"] {
assert!(!s.contains(na), "{name}: {na}");
}
}
}
}
/// **A principal the box runs on, not a person** — the console's own key, the
/// guest, the twin and the front's certify key, the installer's seed, a
/// builder (`dwarves-oden`), a deploy key, a group. gunnar already knows: every
/// fed principal is `machine` (gunnar-server `fed_principals.rs`), a group is
/// `group`, a deploy key `deploy`. Only `user` is a person. Decided from that
/// KIND, never from a list of names.
pub fn is_system_kind(kind: &str) -> bool {
kind != "user"
}
/// **Is this namespace one of the box's own?** gunnar's `owner_kind` on the
/// namespace (thin layer, owner 2026-10-04): the kind of the principal that
/// owns it. A namespace no principal owns (`unspecified`) is nobody's machine
/// and is shown. Until then the page joined the namespaces against the
/// administrator-only identity table, which a person may not read.
pub fn is_system_namespace(owner_kind: &str) -> bool {
!matches!(owner_kind, "user" | "unspecified" | "") && is_system_kind(owner_kind)
}
#[cfg(test)]
mod system_tests {
use super::*;
/// ★ **Internal principals and their namespaces are not in the lists**
/// (owner, 2026-10-02: gunnar-guest, gunnar-twin, installer-seed,
/// dwarves-oden…) — by their KIND, so a person named "gunnar-something"
/// is still shown and a new machine principal is hidden without a code
/// change. An administrator's toggle shows them.
#[test]
fn system_principals_are_known_by_kind_not_by_name() {
for kind in ["machine", "deploy", "group"] {
assert!(is_system_kind(kind), "{kind}");
}
assert!(!is_system_kind("user"));
// A namespace is hidden by its OWNER's kind, as gunnar sends it.
for kind in ["machine", "group", "deploy"] {
assert!(is_system_namespace(kind), "{kind}");
}
for kind in ["user", "unspecified", ""] {
assert!(!is_system_namespace(kind), "a person's or an ownerless namespace was hidden: {kind:?}");
}
let src = std::fs::read_to_string(std::path::Path::new(env!("CARGO_MANIFEST_DIR")).join("src/nav.rs")).unwrap();
let body = &src[src.find("pub fn is_system_kind").unwrap()..src.find("#[cfg(test)]\nmod system_tests").unwrap()];
for name in ["\"gunnar-guest\"", "\"gunnar-twin\"", "\"installer-seed\"", "\"dwarves-oden\""] {
assert!(!body.contains(name), "a hard-coded name list: {name}");
}
for (page, what) in [("access.rs", "Show system principals"), ("usage.rs", "Show system principals")] {
let src = std::fs::read_to_string(std::path::Path::new(env!("CARGO_MANIFEST_DIR")).join("src/pages").join(page)).unwrap();
assert!(src.contains(what) && src.contains("crate::nav::"), "{page}: no system-principal filter with its toggle");
}
}
}
#[cfg(test)]
mod gunfield_tabs {
use super::*;
/// ★ A guest's tabs: no tab on Pulls or Settings is marked; Readme only at home.
#[test]
fn a_page_without_its_own_tab_marks_none() {
let guest = repo_tabs("alice", "field", false, false);
assert_eq!(current_tab(&guest, "/alice/field"), Some("Readme"));
assert_eq!(current_tab(&guest, "/alice/field/tree/docs"), Some("Code"));
assert_eq!(current_tab(&guest, "/alice/field/issues/1"), Some("Issues"));
assert_eq!(current_tab(&guest, "/alice/field/pulls/1"), None, "a guest has no Pulls tab");
assert_eq!(current_tab(&guest, "/alice/field/settings"), None, "nor a Settings tab");
let owner = repo_tabs("alice", "field", true, true);
assert_eq!(current_tab(&owner, "/alice/field/pulls/1"), Some("Pulls"));
assert_eq!(current_tab(&owner, "/alice/field/settings"), Some("Settings"));
}
/// ★ The tabs use it, and a guest's Settings draws only the sign-in line.
#[test]
fn the_tabs_and_a_guests_settings_page_follow_it() {
let repo = crate::code::of(include_str!("pages/repo.rs"));
assert!(repo.contains("crate::nav::current_tab("), "the tabs");
assert!(repo.contains("crate::nav::SETTINGS_SIGN_IN"), "a guest's Settings");
}
}