Rename types to carry their meaning and trim comments
Assisted-by: Claude Opus 5.5 via Claude Code
change nwzvrnswumumqrtpookmtyumwrxxwlyu
commit 1e3f4143d6fc3d5a21fefe1a28849d1b5688b95c
author Alpha Chen <alpha@kejadlen.dev>
date
parent tqmltwzu
diff --git a/src/parallax.rs b/src/parallax.rs
index 8c397b0..fab69cf 100644
--- a/src/parallax.rs
+++ b/src/parallax.rs
@@ -13,23 +13,20 @@ use mlua::serde::Deserializer;
 use crate::Error;
 use crate::server::Server;
 use crate::spec::ServerSpec;
-use crate::spec::Setup;
+use crate::spec::Spec;
 use crate::spec::Variant;
 
 const FENNEL_LUA: &str = include_str!("../vendor/fennel.lua");
 
-/// The table a config returns: the setup spec plus the server and flow
-/// functions.
 #[derive(Debug)]
 struct Config {
     server: Function,
     flow: Function,
-    setup: Setup,
+    spec: Spec,
 }
 
 impl Config {
-    /// Converts after the config's chunk has returned, so there's no Lua
-    /// position to report; the key path locates mistakes instead.
+    /// The chunk has already returned, so key paths stand in for Lua positions.
     fn from_value(path: &Utf8Path, value: Value) -> Result<Config, Error> {
         let invalid = |message: String| Error::ConfigInvalid {
             path: path.to_owned(),
@@ -41,32 +38,25 @@ impl Config {
                 value.type_name()
             )));
         };
-        let server = pop_function(&table, "server")?
+        let server = take_function(&table, "server")?
             .ok_or_else(|| invalid("server must be a function".to_owned()))?;
-        let flow = pop_function(&table, "flow")?
+        let flow = take_function(&table, "flow")?
             .ok_or_else(|| invalid("flow must be a function".to_owned()))?;
-        let setup: Setup = serde_path_to_error::deserialize(Deserializer::new(Value::Table(table)))
+        let spec: Spec = serde_path_to_error::deserialize(Deserializer::new(Value::Table(table)))
             .map_err(|error| {
-                let key_path = error.path().to_string();
-                match error.into_inner() {
-                    mlua::Error::DeserializeError(message) if key_path == "." => invalid(message),
-                    mlua::Error::DeserializeError(message) => {
-                        invalid(format!("{key_path}: {message}"))
-                    }
-                    other => Error::Config(other),
-                }
-            })?;
-        Ok(Config {
-            server,
-            flow,
-            setup,
-        })
+            let key_path = error.path().to_string();
+            match error.into_inner() {
+                mlua::Error::DeserializeError(message) if key_path == "." => invalid(message),
+                mlua::Error::DeserializeError(message) => invalid(format!("{key_path}: {message}")),
+                other => Error::Config(other),
+            }
+        })?;
+        Ok(Config { server, flow, spec })
     }
 }
 
-/// Takes `key` out of the table, so the serde pass never sees it and
-/// unknown-field validation still catches typos.
-fn pop_function(table: &Table, key: &str) -> Result<Option<Function>, mlua::Error> {
+/// Removed before deserializing so unknown-field validation still catches typos.
+fn take_function(table: &Table, key: &str) -> Result<Option<Function>, mlua::Error> {
     let value = table.raw_get::<Value>(key)?;
     table.raw_set(key, Value::Nil)?;
     Ok(match value {
@@ -75,32 +65,27 @@ fn pop_function(table: &Table, key: &str) -> Result<Option<Function>, mlua::Erro
     })
 }
 
-/// A loaded parallax: the executable form of a config. The server
-/// function and flow are handles into the embedded Lua state.
 pub struct Parallax {
     server: Function,
     #[expect(dead_code, reason = "run once the browser driver lands")]
     flow: Function,
-    setup: Setup,
+    spec: Spec,
     name: String,
-    // The handles above only hold weak references; this is what
-    // keeps the VM open.
+    // The function handles are weak; this keeps the VM alive.
     lua: Lua,
 }
 
 impl Parallax {
     pub fn load(path: &Utf8Path) -> Result<Parallax, Error> {
         let source = fs_err::read_to_string(path)?;
-        // The Fennel compiler requires the debug library, which mlua's
-        // safe default omits. Configs are the user's own trusted files,
-        // so the full stdlib is fine here.
+        // Fennel needs the debug library, and configs are the user's own trusted files.
         let lua = unsafe { Lua::unsafe_new() };
 
         let value =
             run_config(&lua, &source, path.as_str()).with_context(|_| format!("loading {path}"))?;
         let config = Config::from_value(path, value)?;
         let name = config
-            .setup
+            .spec
             .name
             .clone()
             .unwrap_or_else(|| default_name(path));
@@ -108,27 +93,21 @@ impl Parallax {
         Ok(Parallax {
             server: config.server,
             flow: config.flow,
-            setup: config.setup,
+            spec: config.spec,
             name,
             lua,
         })
     }
 
-    /// The variants in run order: sorted by name.
     pub fn variants(&self) -> &[Variant] {
-        &self.setup.variants
+        &self.spec.variants
     }
 
     pub fn name(&self) -> &str {
         &self.name
     }
 
-    /// Runs every variant: server up, flow, server down. The flow
-    /// itself joins once the browser driver lands; for now this
-    /// exercises the server lifecycle per variant.
     pub async fn run(&self, output: Option<&Utf8Path>) -> Result<(), Error> {
-        // Runs without --output write to scratch space until the XDG
-        // cache defaults land; nothing should litter the cwd.
         let scratch;
         let root = match output {
             Some(path) => path,
@@ -137,19 +116,17 @@ impl Parallax {
                 scratch.path()
             }
         };
-        for variant in &self.setup.variants {
-            let spec = self.server_spec()?;
-            let server = Server::start(&spec, variant.name.as_str(), root).await?;
+        for variant in &self.spec.variants {
+            let server_spec = self.call_server()?;
+            let server = Server::start(&server_spec, variant.name.as_str(), root).await?;
             // The flow runs here once the browser driver lands.
             server.stop().await?;
         }
         Ok(())
     }
 
-    /// Calls the config's server function and validates what it
-    /// returns. The design calls it once per variant, so a config can
-    /// vary the command per run.
-    fn server_spec(&self) -> Result<ServerSpec, Error> {
+    /// Called per variant, so a config can vary the command per run.
+    fn call_server(&self) -> Result<ServerSpec, Error> {
         let value: Value = self.server.call(()).map_err(Error::ServerFunction)?;
         self.lua.from_value(value).map_err(Error::ServerFunction)
     }
@@ -162,15 +139,10 @@ fn default_name(path: &Utf8Path) -> String {
         .to_owned()
 }
 
-/// Loads the Fennel compiler, runs the config, and returns whatever
-/// its chunk returns.
 fn run_config(lua: &Lua, source: &str, filename: &str) -> Result<Value, mlua::Error> {
     let fennel: Table = lua.load(FENNEL_LUA).set_name("fennel.lua").eval()?;
 
-    // `correlate` makes Fennel emit each statement on its source line,
-    // so syntax and runtime errors report the config's line numbers; a
-    // form that emits several statements would otherwise drift every
-    // following line.
+    // `correlate` keeps errors on the config's own line numbers.
     let compile: Function = fennel.get("compile-string")?;
     let options = lua.create_table()?;
     options.set("filename", filename)?;
@@ -184,9 +156,6 @@ mod tests {
     use super::*;
     use camino::Utf8Path;
 
-    /// A type whose serialization always fails, so deserializing a
-    /// value containing it errors without going through serde's
-    /// validation messages.
     struct FailsSerialize;
 
     impl serde::Serialize for FailsSerialize {
diff --git a/src/server.rs b/src/server.rs
index 484b1ab..5613ced 100644
--- a/src/server.rs
+++ b/src/server.rs
@@ -1,12 +1,5 @@
-//! The server supervisor: spawns each variant's app command in its own
-//! process group, waits for the handshake, and tears the group down
-//! with SIGTERM then SIGKILL.
-//!
-//! The lifecycle is deliberately blocking — `std::process` plus a poll
-//! loop — and the async surface wraps it in `spawn_blocking` so a run
-//! never occupies an executor thread. The child inherits descriptors
-//! straight into `server.log`, so there is no pipe plumbing to drain
-//! and the log is complete once the group is reaped.
+//! Blocking by design behind `spawn_blocking`: the app writes straight
+//! into `server.log`, so there are no pipes to drain.
 
 use std::io;
 use std::os::unix::process::CommandExt as _;
@@ -24,16 +17,11 @@ use serde_json::Value;
 use crate::Error;
 use crate::spec::ServerSpec;
 
-/// How long the group gets to exit after SIGTERM before SIGKILL.
-const GRACE: Duration = Duration::from_secs(5);
-/// How often the handshake file and the child are checked.
-const POLL: Duration = Duration::from_millis(20);
-/// The most bytes of `server.log` quoted in a failure.
-const TAIL: usize = 2048;
+const SIGTERM_GRACE: Duration = Duration::from_secs(5);
+const POLL_INTERVAL: Duration = Duration::from_millis(20);
+const LOG_TAIL_BYTES: usize = 2048;
 
-/// The JSON an app writes to `$PARALLAX_HANDSHAKE` once it is
-/// listening. `data` is opaque to parallax; the flow reads it with
-/// `(app)`.
+/// `data` is opaque here; flows read it with `(app)`.
 #[derive(Debug, Deserialize)]
 pub(crate) struct Handshake {
     #[cfg_attr(
@@ -50,12 +38,11 @@ pub(crate) struct Handshake {
     pub(crate) data: Value,
 }
 
-/// A running app. Dropping it without calling `stop` SIGKILLs its group.
 #[derive(Debug)]
 pub(crate) struct Server {
     handshake: Handshake,
     group: ProcessGroup,
-    _dir: camino_tempfile::Utf8TempDir,
+    _handshake_dir: camino_tempfile::Utf8TempDir,
 }
 
 impl Server {
@@ -117,17 +104,17 @@ impl Server {
         let handshake = match outcome {
             Ok(handshake) => handshake,
             Err(failure) => {
-                // Reaping the group first means its log is complete.
+                // Reaping first completes the log.
                 drop(group);
-                let tail = tail(&log);
+                let tail = log_tail(&log);
                 let variant = variant.to_owned();
                 return Err(match failure {
-                    HandshakeError::Exited(status) => Error::ServerExited {
+                    NotReady::Exited(status) => Error::ServerExited {
                         variant,
                         status,
                         tail,
                     },
-                    HandshakeError::Timeout => Error::ServerTimeout {
+                    NotReady::TimedOut => Error::ServerTimeout {
                         variant,
                         timeout: spec.timeout,
                         tail,
@@ -138,14 +125,12 @@ impl Server {
         Ok(Server {
             handshake,
             group,
-            _dir: dir,
+            _handshake_dir: dir,
         })
     }
 }
 
-/// A child spawned as the leader of its own process group, so signals
-/// reach everything a wrapper like `sh -c` or `bundle exec` started.
-/// Dropping it SIGKILLs the group and reaps the leader.
+/// Signals reach everything a wrapper like `sh -c` or `bundle exec` started.
 #[derive(Debug)]
 struct ProcessGroup {
     leader: Child,
@@ -165,12 +150,12 @@ impl ProcessGroup {
         self.leader.try_wait()
     }
 
-    /// SIGTERM, then the grace period; the drop SIGKILLs any stragglers.
+    /// The drop SIGKILLs whatever outlives the grace period.
     fn terminate(mut self) -> io::Result<()> {
         signal_group(self.pgid, libc::SIGTERM)?;
         let started = Instant::now();
-        while self.try_wait()?.is_none() && started.elapsed() < GRACE {
-            std::thread::sleep(POLL);
+        while self.try_wait()?.is_none() && started.elapsed() < SIGTERM_GRACE {
+            std::thread::sleep(POLL_INTERVAL);
         }
         Ok(())
     }
@@ -183,34 +168,29 @@ impl Drop for ProcessGroup {
     }
 }
 
-/// Why the app never became ready.
-enum HandshakeError {
+enum NotReady {
     Exited(ExitStatus),
-    Timeout,
+    TimedOut,
 }
 
-/// Polls for the handshake until it exists and parses. Apps rename the
-/// file into place, so a successful read is a complete one; anything
-/// unreadable or unparseable — including a missing `url` — keeps
-/// waiting until the deadline. `exited` is `Child::try_wait`; the outer
-/// error is parallax failing to check, the inner one the app failing.
+/// Apps rename the handshake into place, so any parseable read is complete.
 fn wait_for_handshake(
     path: &Utf8Path,
     timeout: Duration,
-    mut exited: impl FnMut() -> io::Result<Option<ExitStatus>>,
-) -> io::Result<Result<Handshake, HandshakeError>> {
+    mut try_wait: impl FnMut() -> io::Result<Option<ExitStatus>>,
+) -> io::Result<Result<Handshake, NotReady>> {
     let started = Instant::now();
     loop {
         if let Some(handshake) = read_handshake(path) {
             return Ok(Ok(handshake));
         }
-        if let Some(status) = exited()? {
-            return Ok(Err(HandshakeError::Exited(status)));
+        if let Some(status) = try_wait()? {
+            return Ok(Err(NotReady::Exited(status)));
         }
         if started.elapsed() >= timeout {
-            return Ok(Err(HandshakeError::Timeout));
+            return Ok(Err(NotReady::TimedOut));
         }
-        std::thread::sleep(POLL);
+        std::thread::sleep(POLL_INTERVAL);
     }
 }
 
@@ -219,8 +199,6 @@ fn read_handshake(path: &Utf8Path) -> Option<Handshake> {
     serde_json::from_str(&text).ok()
 }
 
-/// Signals the whole process group. A missing group is success: there
-/// is nothing left to signal.
 fn signal_group(pgid: i32, signal: libc::c_int) -> io::Result<()> {
     // SAFETY: killpg sends a signal and touches none of our memory.
     if unsafe { libc::killpg(pgid, signal) } == -1 {
@@ -232,9 +210,7 @@ fn signal_group(pgid: i32, signal: libc::c_int) -> io::Result<()> {
     Ok(())
 }
 
-/// The end of the server's log, cut forward to a line boundary so the
-/// quote starts on one.
-fn tail(path: &Utf8Path) -> String {
+fn log_tail(path: &Utf8Path) -> String {
     #[expect(
         clippy::disallowed_methods,
         reason = "a log tail is arbitrary bytes headed for an error message"
@@ -246,14 +222,14 @@ fn tail(path: &Utf8Path) -> String {
     let Ok(bytes) = fs_err::read(path) else {
         return String::new();
     };
-    let start = bytes.len().saturating_sub(TAIL);
+    let start = bytes.len().saturating_sub(LOG_TAIL_BYTES);
     let slice = bytes.get(start..).unwrap_or_default();
     if start == 0 {
         return lossy(slice);
     }
-    let mut split = slice.splitn(2, |&byte| byte == b'\n');
-    split.next(); // the line the window cut into
-    lossy(split.next().unwrap_or_default())
+    let mut lines = slice.splitn(2, |&byte| byte == b'\n');
+    let _cut_line = lines.next();
+    lossy(lines.next().unwrap_or_default())
 }
 
 #[cfg(test)]
@@ -263,8 +239,8 @@ mod tests {
     use super::*;
     use serde_json::json;
 
-    /// A shell step that completes the handshake, for tests about teardown.
-    const HANDSHAKE: &str = r#"printf '{"url":"http://127.0.0.1:1"}' > "$PARALLAX_HANDSHAKE""#;
+    const WRITE_HANDSHAKE: &str =
+        r#"printf '{"url":"http://127.0.0.1:1"}' > "$PARALLAX_HANDSHAKE""#;
 
     fn spec(program: &str, args: &[&str], timeout: Duration) -> ServerSpec {
         ServerSpec {
@@ -278,12 +254,10 @@ mod tests {
         spec("sh", &["-c", script], timeout)
     }
 
-    /// Polls for up to five seconds until no process remains in the
-    /// group, sleeping first since SIGKILLed orphans take a moment to
-    /// be reaped.
+    /// SIGKILLed orphans take a moment to be reaped.
     fn assert_group_gone(pgid: i32) {
         let gone = (0..250).any(|_| {
-            std::thread::sleep(POLL);
+            std::thread::sleep(POLL_INTERVAL);
             // SAFETY: signal 0 only checks that the group exists.
             (unsafe { libc::killpg(pgid, 0) }) == -1
         });
@@ -326,7 +300,7 @@ mod tests {
     #[tokio::test]
     async fn wrappers_leave_no_orphans() {
         let output = camino_tempfile::tempdir().expect("tempdir");
-        let script = format!("sleep 300 & {HANDSHAKE}; sleep 300");
+        let script = format!("sleep 300 & {WRITE_HANDSHAKE}; sleep 300");
         let server = Server::start(
             &sh(&script, Duration::from_secs(5)),
             "desktop",
@@ -342,7 +316,7 @@ mod tests {
     #[tokio::test]
     async fn dropping_a_server_kills_its_group() {
         let output = camino_tempfile::tempdir().expect("tempdir");
-        let script = format!("sleep 300 & {HANDSHAKE}; sleep 300");
+        let script = format!("sleep 300 & {WRITE_HANDSHAKE}; sleep 300");
         let server = Server::start(
             &sh(&script, Duration::from_secs(5)),
             "desktop",
@@ -358,10 +332,8 @@ mod tests {
     #[tokio::test]
     async fn sigterm_runs_before_sigkill() {
         let output = camino_tempfile::tempdir().expect("tempdir");
-        // sh logs from its TERM handler before exiting; SIGKILL first
-        // would kill it before the handler could run. The handshake
-        // comes after the trap, so the handler is installed by then.
-        let script = format!("trap 'echo terminated-by-term' TERM; {HANDSHAKE}; sleep 300");
+        // The handshake follows the trap, so the handler is installed before stop.
+        let script = format!("trap 'echo terminated-by-term' TERM; {WRITE_HANDSHAKE}; sleep 300");
         let server = Server::start(
             &sh(&script, Duration::from_secs(5)),
             "desktop",
@@ -378,9 +350,8 @@ mod tests {
     #[tokio::test]
     async fn sigkill_escalates_when_sigterm_is_ignored() {
         let output = camino_tempfile::tempdir().expect("tempdir");
-        // trap '' leaves SIGTERM ignored, and the fg sleep inherits
-        // that, so only SIGKILL can take the group down.
-        let script = format!("trap '' TERM; {HANDSHAKE}; sleep 300");
+        // sleep inherits the ignored TERM, so only SIGKILL ends the group.
+        let script = format!("trap '' TERM; {WRITE_HANDSHAKE}; sleep 300");
         let server = Server::start(
             &sh(&script, Duration::from_secs(5)),
             "desktop",
@@ -392,7 +363,7 @@ mod tests {
         let started = Instant::now();
         server.stop().await.expect("server stops");
         let waited = started.elapsed();
-        assert!(waited >= GRACE, "stop waited only {waited:?}");
+        assert!(waited >= SIGTERM_GRACE, "stop waited only {waited:?}");
         assert_group_gone(pgid);
     }
 
@@ -463,7 +434,8 @@ mod tests {
     #[tokio::test]
     async fn server_output_lands_in_the_log() {
         let output = camino_tempfile::tempdir().expect("tempdir");
-        let script = format!("echo an-out-line; echo an-err-line >&2; {HANDSHAKE}; sleep 300");
+        let script =
+            format!("echo an-out-line; echo an-err-line >&2; {WRITE_HANDSHAKE}; sleep 300");
         let server = Server::start(
             &sh(&script, Duration::from_secs(5)),
             "desktop",
@@ -490,8 +462,7 @@ mod tests {
 
     #[test]
     fn signalling_reports_errors_other_than_a_missing_group() {
-        // Group 0 is our own; the out-of-range signal is rejected
-        // before anything could be delivered.
+        // Group 0 is our own; the bad signal is rejected before delivery.
         let error = signal_group(0, 9999).expect_err("invalid signal");
         assert_eq!(error.raw_os_error(), Some(libc::EINVAL));
     }
@@ -499,7 +470,7 @@ mod tests {
     #[test]
     fn the_tail_of_a_missing_log_is_empty() {
         let dir = camino_tempfile::tempdir().expect("tempdir");
-        assert_eq!(tail(&dir.path().join("server.log")), "");
+        assert_eq!(log_tail(&dir.path().join("server.log")), "");
     }
 
     #[test]
@@ -509,7 +480,7 @@ mod tests {
         // 300 ten-byte lines; the 2048-byte window cuts into line 95.
         let text: String = (0..300).map(|n| format!("line-{n:04}\n")).collect();
         fs_err::write(&path, text).expect("log written");
-        let quoted = tail(&path);
+        let quoted = log_tail(&path);
         assert!(quoted.starts_with("line-0096\n"), "tail was {quoted:?}");
         assert!(quoted.ends_with("line-0299\n"));
     }
diff --git a/src/spec.rs b/src/spec.rs
index 9990bea..ae6523e 100644
--- a/src/spec.rs
+++ b/src/spec.rs
@@ -11,7 +11,6 @@ use std::time::Duration;
 use serde::Deserialize;
 use serde::de::IgnoredAny;
 
-/// A string that rejected its own emptiness at deserialization time.
 #[derive(Debug)]
 pub struct NonEmptyString(String);
 
@@ -38,7 +37,6 @@ impl<'de> Deserialize<'de> for NonEmptyString {
     }
 }
 
-/// A color scheme parallax emulates for a variant.
 #[derive(Clone, Copy, Debug, Default, Deserialize, PartialEq, Eq)]
 #[serde(rename_all = "lowercase")]
 pub enum Scheme {
@@ -47,8 +45,6 @@ pub enum Scheme {
     Dark,
 }
 
-/// A browser emulation the flow runs under: a named viewport size
-/// paired with a color scheme.
 #[derive(Debug)]
 pub struct Variant {
     pub name: NonEmptyString,
@@ -57,12 +53,9 @@ pub struct Variant {
     pub scheme: Scheme,
 }
 
-/// The data half of the table a config returns, with
-/// variants flattened into run order. The caller pulls `server` and
-/// `flow` out first; a function anywhere else is an error, which is why
-/// it deserializes with mlua's default strict options.
+/// Deserialized with mlua's strict defaults, so a stray function is an error.
 #[derive(Debug)]
-pub(crate) struct Setup {
+pub(crate) struct Spec {
     pub(crate) variants: Vec<Variant>,
 
     #[cfg_attr(
@@ -74,11 +67,9 @@ pub(crate) struct Setup {
     pub(crate) name: Option<String>,
 }
 
-/// What a config's table deserializes from: variants keyed by name,
-/// so the names are data rather than struct fields.
 #[derive(Deserialize)]
 #[serde(deny_unknown_fields)]
-struct SetupTable {
+struct SpecTable {
     variants: HashMap<String, VariantTable>,
 
     #[serde(default)]
@@ -87,8 +78,7 @@ struct SetupTable {
     #[serde(default)]
     name: Option<String>,
 
-    // Already removed by the caller; declared so unknown-field errors
-    // list them among the expected keys.
+    // Already taken by the caller; declared so unknown-field errors list them.
     #[serde(default, rename = "server")]
     _server: Option<IgnoredAny>,
 
@@ -105,14 +95,14 @@ struct VariantTable {
     scheme: Scheme,
 }
 
-impl<'de> Deserialize<'de> for Setup {
+impl<'de> Deserialize<'de> for Spec {
     fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
-        let SetupTable {
+        let SpecTable {
             variants,
             baseline,
             name,
             ..
-        } = SetupTable::deserialize(deserializer)?;
+        } = SpecTable::deserialize(deserializer)?;
         if variants.is_empty() {
             return Err(serde::de::Error::custom("variants must not be empty"));
         }
@@ -127,10 +117,9 @@ impl<'de> Deserialize<'de> for Setup {
                 scheme: variant.scheme,
             });
         }
-        // Lua tables don't preserve key order, and no variant depends
-        // on another's state, so sorted name order is the run order.
+        // Lua tables are unordered, so sorted names are the run order.
         parsed.sort_unstable_by(|a, b| a.name.as_str().cmp(b.name.as_str()));
-        Ok(Setup {
+        Ok(Spec {
             variants: parsed,
             baseline,
             name,
@@ -138,8 +127,7 @@ impl<'de> Deserialize<'de> for Setup {
     }
 }
 
-/// Where comparison snapshots come from; see the design doc's GitHub
-/// releases section.
+/// See the design doc's GitHub releases section.
 #[derive(Debug, Deserialize)]
 #[serde(deny_unknown_fields)]
 #[expect(dead_code, reason = "read once the GitHub task lands")]
@@ -150,12 +138,8 @@ pub(crate) struct BaselineSource {
     pub(crate) asset: Option<NonEmptyString>,
 }
 
-/// The table the registered server function returns, once per
-/// variant.
 #[derive(Clone, Debug)]
 pub struct ServerSpec {
-    /// The first word of `:cmd`, split off so an empty command is
-    /// unrepresentable.
     pub program: String,
     pub args: Vec<String>,
 
@@ -163,7 +147,6 @@ pub struct ServerSpec {
 }
 
 impl ServerSpec {
-    /// The command as the config spelled it, for messages.
     pub fn command_line(&self) -> String {
         std::iter::once(&self.program)
             .chain(&self.args)
@@ -232,7 +215,7 @@ mod tests {
                 baseline = {repo = "kejadlen/ketchup"},
             }"#,
             |lua, value| {
-                let setup: Setup = lua.from_value(value).expect("setup table");
+                let setup: Spec = lua.from_value(value).expect("setup table");
                 assert_eq!(setup.variants.len(), 2);
                 assert_eq!(setup.variants[0].name.as_str(), "desktop");
                 assert_eq!(setup.variants[0].scheme, Scheme::Dark);
@@ -285,7 +268,7 @@ mod tests {
         for (body, fragment) in cases {
             with_value(body, |lua, value| {
                 let error = lua
-                    .from_value::<Setup>(value)
+                    .from_value::<Spec>(value)
                     .expect_err("expected an invalid setup table");
                 assert!(
                     error.to_string().contains(fragment),
@@ -301,7 +284,7 @@ mod tests {
             r#"return {variants = {desktop = {viewport = {1280, 900}}}, name = (function() end)}"#,
             |lua, value| {
                 let error = lua
-                    .from_value::<Setup>(value)
+                    .from_value::<Spec>(value)
                     .expect_err("functions cannot appear in setup");
                 assert!(error.to_string().contains("unsupported"));
             },