Repository navigation
feat(infra): Spacemacs joins the config environments; GUI config-boot comparisons - #403
Conversation
Spacemacs is the second config environment and stresses the abstraction exactly where it should: the distribution IS ~/.emacs.d with no DOOMLOCALDIR-style redirect, writable state lives inside .emacs.d itself. The session model is therefore a HOME overlay: <state>/.emacs.d is a real directory whose entries symlink into the sealed tree (dotfiles included -- .lock is checked in; .git skipped), elpa shared read-only from the extracted package state (Doom's straight/ trick generalized), .cache copied per session. Three real fixes fell out of the first live session, each now encoded where it belongs: - copy_tree_writable: fs::copy reproduces the sealed source's read-only bits, and Spacemacs rewrites .cache/spacemacs-buffer.el on every boot -- session-seeded copies must restore write bits. - The deterministic dotfile carries the lexical-binding cookie on line 1 (below the header comment it is invisible, and GNU pops a *Warnings* window no paired comparison could match) and pins the home buffer's wall-clock footer via a :before advice on display-startup-time -- its unless-guard then keeps the constant. - The existing Doom lifecycle moved behind a ConfigEnvironment trait (name/tree/home/session_env/session_args/prepare_session_state) with the shared machinery (spec parse, shallow fetch, seal, MANIFEST) in config_env::common; adding a third distribution (Centaur) is now one file, one spec, one CLI arm. The TUI boot test mounts both editors through the overlay, settles past deferred startup before first input (the Doom which-key lesson), and asserts exact display parity -- green against the pinned syl20bnr/spacemacs develop @ ab5bb4ce.
…dening Both fixtures now mount in real GUI sessions through the same ConfigEnvironment contract the TUI tests use: per-session HOME, XDG directories pinned inside the session state (GTK3/fontconfig/GLib never read the operator's real config), NO_AT_BRIDGE/GTK_MODULES cleared -- the accessibility bridge spawns a session bus under the redirected XDG and wedges GUI startup in do_wait -- and quit escapes past config frameworks' kill-emacs confirmations. Three findings from the first live runs, each encoded where it belongs: - The stock GUI harness defaults to -Q -l, which suppresses the very user-init the config tests exist to boot; the plan now sets its own args (mount args then -l probe, no -Q). - The GTK3 GNU oracle is an X11 build: it cannot mount a Wayland-only weston at all. GNU GUI under weston has never actually run on CI -- one more leg of the still-open GUI cluster. The config tests use the Xvfb backend, where GNU GUI is proven (live-font artifacts). - GNU GUI does boot doom through the harness (frame opens, probe fires), but the scratch renders no visible text under software Xvfb/llvmpipe, and spacemacs GUI init never completes there within 300s while its TUI boots in seconds. Both tests land ignored with the status documented, mirroring the minor-mode repro convention. The TUI spacemacs boot and doom face comparisons stay green.
Chased the empty-scratch wall to ground. GNU GUI + the sealed doom fixture under Xvfb boots doom successfully -- 'Doom loaded 14 packages across 3 modules in 0.310s' in *Messages*, zero warnings with the seeded DOOMLOCALDIR, and the pixel capture shows the themed frame (doom's dark theme, ~50% coverage). The harness's window-text probe reads an intentionally-empty buffer: the minimal fixture doom (no DOOMDIR) sets initial-scratch-message nil, so no window-text needle can ever fire. Neomacs on the same fixture shows the vanilla scratch text -- itself a candidate divergence to settle. Both GUI config tests stay ignored with the design requirement documented: the comparison probe must observe doom-GUI state (dashboard buffer presence, modeline face, or pixel diff), not selected-window text. The isolation hardening (XDG pins, NO_AT_BRIDGE, kill-emacs escapes, Xvfb backend for the X11 GTK oracle) is all real and stays.
The empty-scratch wall was two bugs stacked, neither a rendering bug: 1. The probe fixture had unbalanced parens after string surgery — GNU failed to load it (end-of-file), the tick never armed, and every harness run timed out from a probe that was never running. The needle redesign (scan *Messages* for the framework's finish line, since minimal doom sets initial-scratch-message nil and ships no dashboard without a DOOMDIR) rides along in the rewritten fixture. 2. With the fixture loading, doom GUI parity passes in 15s — GNU and Neomacs boot the sealed doom fixture on isolated Xvfb, identical frame/buffer/visible-text state. Spacemacs GUI remains open, GNU-side only: in the harness GNU never opens its X connection (no socket fd; gmain/gdbus idle; pango parked) while the doom sibling passes on identical env and TUI spacemacs parity is green — a spacemacs-specific pre-display block to chase separately.
There was a problem hiding this comment.
Copilot review overview
🟡 Changes recommended
The checked-in .spacemacs fixture is unbalanced by one paren (fails to load), and copy_tree calls std::os::unix::fs::symlink directly, which breaks the Windows cargo check --workspace gate.
Get a fresh assessment by requesting another Copilot review.
Review effort: Balanced
Findings: 2
Open (4)
What changed in this PR
This PR generalizes neomacs-infra's single Doom Emacs test fixture into a pluggable ConfigEnvironment abstraction and adds Spacemacs as a second environment, plus GUI boot-parity tests that compare GNU Emacs against neomacs for each config. The Doom-specific logic previously living inline in lib.rs is split into a config_env module (mod/common/doom/spacemacs), with shared lifecycle (spec parse, shallow fetch-by-SHA, seal, MANIFEST) in common. Spacemacs mounts through a HOME overlay (<state>/.emacs.d symlinks into a sealed tree; elpa shared read-only, .cache copied writable), driven by a deterministic .spacemacs dotfile that suppresses the first-run wizard.
Changes:
- Extracts the fixture lifecycle into
config_envwith aConfigEnvironmenttrait and sharedcommonhelpers, including a newcopy_tree_writable(restores write bits so sessions can rewrite seeded state). - Adds the Spacemacs environment (HOME-overlay session model), its spec, and a deterministic dotfile fixture.
- Adds TUI (
spacemacs_boot.rs) and GUI (config_env_boot.rs+config-env-boot.el) boot-parity tests, and generalizes theinfraCLI to materialize/status any named environment.
| File | Description |
|---|---|
| crates/neomacs-infra/src/lib.rs | Slims to crate docs + workspace_root; re-exports the new config_env API. |
| crates/neomacs-infra/src/config_env/mod.rs | New ConfigEnvironment trait, XDG isolation env, NAMES/open_by_name. |
| crates/neomacs-infra/src/config_env/common.rs | Shared spec/seal/fetch/copy lifecycle; copy_tree uses a raw unix symlink call (Windows-breaking). |
| crates/neomacs-infra/src/config_env/doom.rs | Doom environment moved onto the trait, using shared common helpers. |
| crates/neomacs-infra/src/config_env/spacemacs.rs | New Spacemacs environment with HOME-overlay session model. |
| crates/neomacs-infra/src/bin/infra.rs | Generic materialize/status over NAMES; top-level usage string has a stray pipe. |
| crates/neomacs-infra/spacemacs-spec.toml | Pins the Spacemacs repo/revision. |
| crates/neomacs-infra/fixtures/spacemacs-dotfile.el | Deterministic dotfile; dotspacemacs/user is missing a closing paren. |
| crates/neomacs-gui-tests/tests/config_env_boot.rs | GUI boot-parity test for Doom (active) and Spacemacs (ignored). |
| crates/neomacs-gui-tests/fixtures/config-env-boot.el | Shared GUI boot probe dumping window text + frame geometry. |
| crates/neomacs-tui-tests/tests/spacemacs_boot.rs | TUI boot-parity test; unused OsString import. |
| crates/neomacs-tui-tests/tests/{tui,face_color_test,minor_mode_order_repro}.rs | Wire in the new module/trait imports. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
| (advice-add 'configuration-layer/display-startup-time :before | ||
| (lambda (&rest _) | ||
| (setq configuration-layer--spacemacs-startup-time 0.500 | ||
| dotspacemacs--user-config-elapsed-time 0.500))) |
| if meta.is_symlink() { | ||
| let target = fs::read_link(source) | ||
| .map_err(|error| format!("readlink {}: {error}", source.display()))?; | ||
| std::os::unix::fs::symlink(&target, destination).map_err(|error| { |
| eprintln!("usage: infra <materialize {} [path]|status>", { | ||
| let mut usage = String::new(); | ||
| for name in neomacs_infra::config_env::NAMES { | ||
| usage.push_str(&format!("<{name}>|")); | ||
| } | ||
| usage | ||
| }); |
| use crate::support; | ||
| use neomacs_infra::config_env::ConfigEnvironment as _; | ||
| use neomacs_tui_tests::{TuiLaunch, TuiSession, TuiTempDirectory}; | ||
| use std::ffi::OsString; |


What
Second config environment for
neomacs-infra, plus GUI-boot parity tests for both configs.The abstraction
ConfigEnvironmenttrait (session contract:name/tree/home/session_env/session_args/prepare_session_state) with shared lifecycle inconfig_env::common(spec parse, shallow fetch-by-SHA, seal, MANIFEST). Adding Centaur Emacs later = one file + one spec + one CLI arm, zero consumer edits.Spacemacs session model
No
DOOMLOCALDIR-style redirect exists — writable state lives inside.emacs.ditself. Sessions mount through a HOME overlay:<state>/.emacs.dis a real directory whose entries symlink into the sealed tree (dotfiles included —.lockis checked in;.gitskipped),elpashared read-only from extracted package state (Doom'sstraight/trick generalized),.cachecopied per session. Deterministic.spacemacsdotfile suppresses the first-run wizard, pins the home buffer's wall-clock footer, carries thelexical-bindingcookie on line 1.Real fixes found by the first live sessions
copy_tree_writable—fs::copyreproduces sealed read-only bits; Spacemacs rewrites.cache/spacemacs-buffer.elevery bootNO_AT_BRIDGE/GTK_MODULEScleared (the accessibility bridge wedges GUI startup indo_wait), quit escapes past config frameworks' kill-emacs confirmationscargo checkgating (is_sealed/symlink/seal) into the new module structureResults