From 87a689c16a06e7a26e4dfef92d348c082dc0ae7b Mon Sep 17 00:00:00 2001 From: "@permadeath.com" Date: Sat, 5 Sep 2026 08:51:10 -0400 Subject: [PATCH] feat(about): name the project's page beside its repo `homepage` in `Cargo.toml` was the repository stated twice; it is `atgc.codes` now, so `atgc about` shows a `site` line under `repo`. The panel's contents moved into `art` on the way, since the terminal drawn on the site is that same panel and had its own copy of it. The test that held `homepage` equal to `repository` now holds it equal to the address Astro is configured to publish at. Co-Authored-By: Claude Opus 5 (1M context) Change-Id: I03e65156f7b97174bc964b0fe4c3899516a613e5 --- Cargo.toml | 7 +++- src/art.rs | 53 ++++++++++++++------------ src/cmd/about.rs | 79 +++++++++++++++++---------------------- src/html/site/mod.rs | 17 ++++----- tests/release_metadata.rs | 31 ++++++++++----- 5 files changed, 98 insertions(+), 89 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 0e1eb46..2e32341 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,8 +14,11 @@ rust-version = "1.89" description = "ATproto Git Client. A CLI for Tangled repos and pull requests." license = "MIT OR Apache-2.0" repository = "https://tangled.org/permadeath.com/atgc" -# The repository again, on purpose, until there is a project page to point at. -homepage = "https://tangled.org/permadeath.com/atgc" +# The project page, which exists now: `atgc.codes` is built out of `www/` in +# this tree. It is where a stranger is sent and the repository is where a +# reader is sent, and `CARGO_PKG_HOMEPAGE` is the one place either the panel +# `atgc about` draws or the card a login lands on can read it from. +homepage = "https://atgc.codes" readme = "README.md" keywords = ["atproto", "tangled", "git", "cli", "pull-request"] categories = ["command-line-utilities", "development-tools"] diff --git a/src/art.rs b/src/art.rs index e66de25..75e447d 100644 --- a/src/art.rs +++ b/src/art.rs @@ -343,6 +343,27 @@ pub(crate) fn width_of(line: &Line<'_>) -> usize { } } +/// What the panel says: the tagline, and the three facts about the project +/// somebody might act on. +/// +/// Here rather than in [`crate::cmd::about`] because the terminal on +/// atgc.codes is that command's own drawing — `html::site` calls this, under +/// the `www` feature — and not a picture of it: a page showing a panel the +/// command does not print would be a screenshot of software nobody has. Both `repo` and `site` are read +/// out of `Cargo.toml`, so the crate's metadata, the panel and the card a +/// login lands on cannot disagree about where atgc lives. +pub(crate) fn panel_lines() -> [Line<'static>; 7] { + [ + Line::Blank, + Line::Tagline(env!("CARGO_PKG_DESCRIPTION")), + Line::Blank, + Line::Field("version", env!("CARGO_PKG_VERSION")), + Line::Field("repo", env!("CARGO_PKG_REPOSITORY")), + Line::Field("site", env!("CARGO_PKG_HOMEPAGE")), + Line::Blank, + ] +} + /// The name, found inside a phrase: A then T then G then C, once each and /// in that order. /// @@ -459,20 +480,6 @@ pub(crate) fn draw_bare(lines: &[Line<'_>], content: i32) -> Canvas { mod tests { use super::*; - /// The panel `atgc about` builds, so the tests below are measuring the - /// real thing rather than a stand-in that could drift from it. The - /// command owns the *contents*; what is checked here is the drawing. - fn panel_lines() -> [Line<'static>; 6] { - [ - Line::Blank, - Line::Tagline(env!("CARGO_PKG_DESCRIPTION")), - Line::Blank, - Line::Field("version", env!("CARGO_PKG_VERSION")), - Line::Field("repo", env!("CARGO_PKG_REPOSITORY")), - Line::Blank, - ] - } - /// The frame ceiling `cmd::about` draws at, repeated rather than imported /// so the canvas tests exercise the largest grid ever asked for without /// `art` depending on a command module's constants. If those move, these @@ -847,7 +854,7 @@ mod tests { let expected = lines.iter().filter(|l| width_of(l) > 0).count(); assert_eq!(rendered.len(), expected); - assert_eq!(expected, 3, "tagline, version, repo"); + assert_eq!(expected, 4, "tagline, version, repo, site"); for row in &rendered { assert!(!row.trim().is_empty()); for border in ['╭', '╮', '╰', '╯', '│', '─'] { @@ -857,14 +864,14 @@ mod tests { ); } } - // The repo URL is the thing someone might copy, so it must arrive - // whole rather than cropped to the canvas. - assert!( - rendered - .iter() - .any(|r| r.contains(env!("CARGO_PKG_REPOSITORY"))), - "{rendered:?}" - ); + // The two URLs are the things someone might copy, so they must + // arrive whole rather than cropped to the canvas. + for url in [env!("CARGO_PKG_REPOSITORY"), env!("CARGO_PKG_HOMEPAGE")] { + assert!( + rendered.iter().any(|r| r.contains(url)), + "{url} is not in {rendered:?}" + ); + } } /// The bare canvas is exactly as wide as its widest line, which is what diff --git a/src/cmd/about.rs b/src/cmd/about.rs index c5c1552..276a0ad 100644 --- a/src/cmd/about.rs +++ b/src/cmd/about.rs @@ -13,7 +13,7 @@ //! callback page came to import a *command* module for it. It is drawn, not //! stored, in both places, and now from one place. -use crate::art::{self, Canvas, GUTTER, Line, PAD, draw_bare, draw_panel, width_of}; +use crate::art::{self, Canvas, GUTTER, PAD, draw_bare, draw_panel, panel_lines, width_of}; use anyhow::Result; use terminal_size::{Height, Width, terminal_size}; @@ -97,15 +97,27 @@ fn frame(panel_w: i32, panel_h: i32) -> (i32, i32) { (width, height) } +/// The row the panel's top edge goes on, in a frame of `height` rows: the +/// slack split between the two, floored. +/// +/// The flooring is the point rather than an off-by-one. A block sitting +/// below the arithmetic middle reads as sagging, and this is what stops it +/// ever being there: the field left above the panel is never deeper than the +/// field left below it. Whether the panel also gets the half row of *lift* +/// an optical centre wants is a matter of parity and belongs to the panel +/// rather than to this function — [`art::snap_rows`] returns none but odd +/// heights, so an even panel is lifted and an odd one, which is what +/// [`art::panel_lines`] builds today, lands on the exact middle row. +/// +/// On the short unsnapped heights the slack is a row or two and the panel +/// sits wherever it fits; nudging it further there would only shove it +/// against the top edge. +fn top_of(height: i32, panel_h: i32) -> i32 { + (height - panel_h) / 2 +} + pub(crate) fn about() -> Result<()> { - let lines = [ - Line::Blank, - Line::Tagline(env!("CARGO_PKG_DESCRIPTION")), - Line::Blank, - Line::Field("version", env!("CARGO_PKG_VERSION")), - Line::Field("repo", env!("CARGO_PKG_REPOSITORY")), - Line::Blank, - ]; + let lines = panel_lines(); let content = lines.iter().map(width_of).max().unwrap_or(0) as i32; let panel_w = content + 2 * PAD + 2; @@ -123,16 +135,8 @@ pub(crate) fn about() -> Result<()> { } else { let mut canvas = Canvas::new(width, height); art::draw_field(&mut canvas); - // Centered both ways, and the flooring here is the point rather than - // an off-by-one: a block placed at the exact arithmetic middle reads - // as sitting low, so it wants half a row of lift. It gets exactly - // that whenever the slack is odd, which [`snap_rows`] quietly - // guarantees — every height it returns is odd, and the panel's is - // even — so the field ends up one row deeper below the panel than - // above it. On the short unsnapped heights the slack is only a row - // or two and the panel sits wherever it fits; nudging it further - // there would just shove it against the top edge. - let top = (height - panel_h) / 2; + // Centered both ways; see [`top_of`] for which way the odd row goes. + let top = top_of(height, panel_h); draw_panel(&mut canvas, (width - panel_w) / 2, top, panel_w, &lines); canvas }; @@ -144,34 +148,21 @@ pub(crate) fn about() -> Result<()> { #[cfg(test)] mod tests { - use super::{Line, art, env_dimension}; - - /// The same panel `about` builds, so the geometry tests are measuring the - /// real thing rather than a stand-in that could drift from it. - fn panel_lines() -> [Line<'static>; 6] { - [ - Line::Blank, - Line::Tagline(env!("CARGO_PKG_DESCRIPTION")), - Line::Blank, - Line::Field("version", env!("CARGO_PKG_VERSION")), - Line::Field("repo", env!("CARGO_PKG_REPOSITORY")), - Line::Blank, - ] - } + use super::{art, env_dimension}; + use crate::art::panel_lines; - /// The comment in `about` leans on this: the panel is an even number of - /// rows and every snapped height is odd, so the slack is always odd and - /// the flooring centering lifts the panel half a row. + /// What [`super::top_of`] promises: the panel never sags. At every frame + /// height it can be drawn in — snapped or not — the field above it is no + /// deeper than the field below. #[test] - fn snapped_heights_are_odd() { - for rows in 1..=super::MAX_ROWS { - assert_eq!( - art::snap_rows(rows) % 2, - 1, - "art::snap_rows({rows}) is even" - ); + fn the_panel_never_sits_below_the_middle() { + let panel_h = panel_lines().len() as i32 + 2; + for rows in panel_h..=super::MAX_ROWS { + let above = super::top_of(rows, panel_h); + let below = rows - panel_h - above; + assert!(above >= 0, "{rows} rows put the panel at {above}"); + assert!(above <= below, "{rows} rows: {above} above, {below} below"); } - assert_eq!(panel_lines().len() as i32 % 2, 0, "panel height is even"); } /// Fixed points: a height that already spans whole half turns is left diff --git a/src/html/site/mod.rs b/src/html/site/mod.rs index 245152f..e339066 100644 --- a/src/html/site/mod.rs +++ b/src/html/site/mod.rs @@ -26,7 +26,7 @@ //! quickstart — is `.astro` in `www/src`, editable by anybody, with no Rust //! rebuild in the loop. -use crate::art::{self, Canvas, Line, PAD, width_of}; +use crate::art::{self, Canvas, PAD, panel_lines, width_of}; use crate::html::brand::{self, Scheme}; /// The one line somebody is meant to leave with. Stated once, here, and @@ -65,14 +65,7 @@ const DEMO_COLUMNS: i32 = 88; const DEMO_ROWS: i32 = 25; fn about_demo() -> String { - let lines = [ - Line::Blank, - Line::Tagline(env!("CARGO_PKG_DESCRIPTION")), - Line::Blank, - Line::Field("version", env!("CARGO_PKG_VERSION")), - Line::Field("repo", REPO), - Line::Blank, - ]; + let lines = panel_lines(); let content = lines.iter().map(width_of).max().unwrap_or(0) as i32; let panel_w = content + 2 * PAD + 2; let panel_h = lines.len() as i32 + 2; @@ -239,7 +232,11 @@ mod tests { assert!(demo.contains("atgc"), "no wordmark in the lid"); assert!(demo.contains('╭') && demo.contains('╯'), "no panel"); assert!(demo.contains(""), "the border is not muted"); - assert!(demo.contains(env!("CARGO_PKG_REPOSITORY")), "no repo line"); + assert!(demo.contains(REPO), "no repo line"); + // The site line too, which is this page's own address: Astro knows + // where it serves from and is handed no fact about it, but the panel + // drawn here prints what the command prints. + assert!(demo.contains(env!("CARGO_PKG_HOMEPAGE")), "no site line"); assert!( demo.contains(env!("CARGO_PKG_VERSION")), "the demo is stale" diff --git a/tests/release_metadata.rs b/tests/release_metadata.rs index ea0e676..5ae5e75 100644 --- a/tests/release_metadata.rs +++ b/tests/release_metadata.rs @@ -106,16 +106,27 @@ fn readme_release_line_sits_under_the_lockup() { ); } -/// `homepage` deliberately repeats `repository`, so the interesting failure is -/// one of them moving without the other: a homepage still pointing at the old -/// host reads as intentional. When there is a real project page to point at, -/// this test is the thing that has to be deleted to allow it, which is the -/// point — the divergence becomes a decision instead of an oversight. +/// `homepage` used to repeat `repository`, and the test here said that the +/// day there was a real project page to point at, this is what would have to +/// be rewritten to allow it. That day was 2026-09-03. The homepage is now +/// `atgc.codes`, which the tree also builds and deploys, so the fact worth +/// pinning is no longer that the two agree but that the address the binary +/// hands out is the address the site is published at: `astro.config.mjs` +/// resolves every canonical URL from its own `site`, and a binary pointing +/// somewhere else would be pointing at a host nobody deploys to. +/// +/// Skipped when `www/` is absent, which is the packaged crate: the tarball +/// ships `src/` and not the site. #[test] -fn homepage_is_still_the_repository() { - assert_eq!( - env!("CARGO_PKG_HOMEPAGE"), - env!("CARGO_PKG_REPOSITORY"), - "homepage and repository have drifted apart; if that is deliberate, delete this test" +fn the_homepage_is_where_the_site_is_published() { + let config = std::path::Path::new(env!("CARGO_MANIFEST_DIR")).join("www/astro.config.mjs"); + let Ok(text) = std::fs::read_to_string(&config) else { + return; + }; + let declared = format!("site: '{}'", env!("CARGO_PKG_HOMEPAGE")); + assert!( + text.contains(&declared), + "{} does not declare {declared:?}; the crate's homepage is not where the site is built", + config.display() ); } -- knotmirror 0.0.1