Skip to content

Wave 4: TUI, snapper, --from/--to, declarative installs#7

Merged
AdamJHall merged 25 commits into
mainfrom
wave4/tui-snapper-fromto-declarative
Jun 22, 2026
Merged

Wave 4: TUI, snapper, --from/--to, declarative installs#7
AdamJHall merged 25 commits into
mainfrom
wave4/tui-snapper-fromto-declarative

Conversation

@AdamJHall

Copy link
Copy Markdown
Owner

Wave 4 — TUI, snapper, --from/--to, and declarative install sources

Six features built by parallel worktree agents (TDD, idiomatic Go), merged into one branch. Full suite green: gofmt -l . clean, go vet ./..., go build, go test ./... all pass; config.example.yaml validates.

Features

Feature Highlights
A TUI conversion New internal/tui/ bubbletea viewport (header/footer, follow/autoscroll, resize). ui output routed through a swappable sink + teaWriter pump. Behind a TTY check + --plain; plain mode stays byte-for-byte unchanged. Prompts run first, in plain mode (two bubbletea programs can't share the terminal).
B Snapper provisioning New Phase-B stage internal/stages/snapper.go (Order 25). No-op unless btrfs layout and snapshots: snapper; otherwise installs snapper/snap-pac, grep-guarded create-config, retention, enables the timeline/cleanup timers. Closes the Wave 2 gap.
C --from/--to resume New stages.Within post-filter (name-or-number bounds, inclusive). --only still wins; composes with --skip/disable.
D1 Explicit pacstrap Replaces pacstrap_extra + the in-code base set with a verbatim required list; pacstrap_extra now errors with a migration hint. Advisory preflight warnings (never injection).
D2 Explicit flatpak_remotes Complete remote list (no implicit Flathub) + per-app remote:appid refs, validated at config time.
D3 Explicit kernel.base Replaces hardcoded Kernels: [linux]; kernel.default must be in base ∪ packages.

Notable integration work (hand-merged centrally)

  • main.go A↔C auto-merged (A kept the stages.Select line stable; C's Within slots right after).
  • Required-field fixture conflicts: D1 (pacstrap) and D3 (kernel.base) both made a field required, colliding in ~15 shared inline-YAML fixtures — resolved by union, except archinstall_test.go where a blind union would have kept the now-illegal pacstrap_extra key.
  • Registry coupling: B's snapper stage (Order 25) landed after C's tests were written, so TestRegistry/TestWithin expectations were centrally updated.
  • Semantic stitch: D1's preflight "no kernel" advisory extended to also consider D3's kernel.base.

Deferred (deliberate)

  • Remote/layered configuration (the one remaining big item).
  • VM validation: all Wave 2–4 reverse-engineered archinstall shapes — now including snapper timer/set-config unit names — still need a real archinstall 4.3 QEMU run before hardware.

See docs/extensibility-review.md → "Wave 4 retrospective" for full notes.

🤖 Generated with Claude Code

AdamJHall and others added 25 commits June 21, 2026 21:27
Phase B stage (order 25) that provisions Snapper for btrfs roots:
installs snapper + snap-pac, creates the root config (grep-guarded),
sets sane retention, and enables the timeline/cleanup timers. A clean
no-op unless layout is btrfs and btrfs.snapshots is snapper (nil-Btrfs
guarded). Closes the Wave 2 gap left at archinstall.go's snapshot hook.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
New stages.Within post-filter narrows the Select result to an
inclusive [from,to] window, resolving each bound by name or
order-number like --only. --only still wins; --from/--to compose
with --skip/disable. Wired as one filter line after Select in
runPhase plus two persistent flags.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
flatpak_remotes is now the complete remote list (no implicit flathub);
each flatpaks entry is a remote:appid ref installed from its named
remote. Validate the ref shape and that the remote is declared.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
bubbletea viewport with follow/autoscroll, WindowSizeMsg resize, and
stage/output/done messages. A teaWriter pumps Runner output into the
model so long installers stay live without going to os.Stdout. Promote
bubbletea/bubbles/x-term from indirect to direct deps.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add SetSink so Header/Step/OK/Info/Warn/Error flow to the TUI viewport
in TUI mode and to os.Stderr in plain mode. Plain mode (sink nil) is
unchanged; a test asserts byte-for-byte output.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
runPhase enters the bubbletea alt-screen on a TTY (skipped by --plain,
--dry-run, and interactive Phase A installs that prompt). Stage loop
extracted to runStages, shared by plain and TUI paths. Update the
CLAUDE.md gotcha to the viewport-sink rule.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Render archinstall Kernels from cfg.Kernel.Base instead of the
hardcoded [linux]. Add kernel.base (required, min=1) and extend the
kernel.default membership check to base union packages.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The pacstrap set is now a required list rendered verbatim into the
archinstall config; nothing is prepended in code. The removed base six
must be listed explicitly. The old pacstrap_extra key now errors with a
rename-to-pacstrap migration message for one release.

preflight emits advisory warnings (never injection) for recommended-but-
absent entries: AUR build deps, networkmanager, microcode, a kernel.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
# Conflicts:
#	internal/archinstall/archinstall_test.go
#	internal/archinstall/bootloader_render_test.go
#	internal/archinstall/btrfs_test.go
#	internal/archinstall/encryption_test.go
#	internal/archinstall/plain_test.go
#	internal/archinstall/testdata/configs/btrfs-subvols.yaml
#	internal/archinstall/testdata/configs/ext4-sata.yaml
#	internal/archinstall/testdata/configs/lvm-zram.yaml
#	internal/archinstall/testdata/configs/multi-disk-lvm.yaml
#	internal/archinstall/testdata/configs/single-disk-lvm.yaml
#	internal/archinstall/volumes_test.go
#	internal/config/disks_test.go
#	internal/config/encryption_test.go
#	internal/config/selectors_config_test.go
#	internal/stages/hooks_expand_test.go
#	internal/stages/hooks_test.go
#	internal/stages/rootdevice_test.go
#	internal/stages/swap_test.go
# Conflicts:
#	internal/config/config.go
…landed

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The light e2e job renders its own config.yaml inline; Wave 4 made
pacstrap and kernel.base required, so validate now rejects it.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The viewport model is value-copied by bubbletea on every Update; a non-zero
value strings.Builder panics ('illegal use of non-zero Builder copied by
value'). Make buf a *strings.Builder so only the pointer is copied.

The alt-screen also owns stdin, so sudo's password prompt (the one interactive
Phase B prompt) corrupted the view. Cache sudo credentials in normal terminal
mode before starting the program, with a 1-min keep-alive.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Hoist the destructive Phase A prompts (ERASE confirmation + user/root
password) into Context.CollectInstallPrompts, called in normal terminal
mode before the alt-screen; the archinstall stage falls back to it inline
for the plain path. Drops the willPrompt gate so install gets the viewport
(including the streamed archinstall pacstrap).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The alt-screen TUI owns stdin, so interactive subprocess prompts (e.g. the
cachyos-repo.sh Y/N questions in user repos[].setup scripts) can't be answered
inside it. Cleanly forwarding them would need a full PTY multiplexer for
arbitrary, unpredictably-interactive shell blobs — brittle and heavy for a
single-machine tool, and exactly what the original design avoided.

Revert Agent A's TUI plus the panic/sudo/prompt-hoist follow-ups: remove
internal/tui, the ui output sink, --plain, sudo pre-cache and install-prompt
hoist. Output once again streams stdout/stderr straight through (Runner.Out nil),
so subprocess prompts read from the real terminal. The rest of Wave 4 (snapper,
--from/--to, declarative pacstrap/flatpak/kernel) is unaffected.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Bind the lipgloss renderer to os.Stderr so colour detection matches where
output lands (clean plaintext when redirected, colour on a TTY); add --no-color
and honour NO_COLOR. Frame each run with a banner (version · phase · stage count,
DRY RUN badge) and a summary with total time; print an [i/n] progress header and
elapsed time per stage, and a red failure summary naming the stage that failed.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@AdamJHall AdamJHall merged commit 255c708 into main Jun 22, 2026
7 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant