Files
Archduino/docs/superpowers/specs/2026-04-14-plan3b-install-modules-design.md

9.7 KiB

Plan 3b — Install Modules (Heavy Tier) Design

Status: Approved 2026-04-14 Scope: Port 2 of the 3 remaining Bash install modules (neovim, renode). The third (scaffold) is deferred to Plan 4, where its functionality is absorbed natively into the archduino new and archduino theme subcommands rather than shipped as bash helper scripts.

Goal

Port the neovim and renode Bash modules to Go, each implementing the module.Module four-phase contract, unit-tested against a fake platform.Runner. Add one small platform helper (WriteUserFile), extend config.Config with the new modules' inputs, extend tui.Theme so the wizard's theme selection flows into NvChad's chadrc.lua, and register the two modules in the install command.

Architecture

  • neovim: pacman-install neovim (Arch ships 0.11.x, no upstream-tarball fallback); clone the NvChad starter to ~/.config/nvim if no existing config; if a non-NvChad config is detected, back it up to ~/.config/nvim.bak.<RFC3339> and install fresh (no LazyVim-specific salvage — deliberate scope cut); strip the starter's nvim-lspconfig block (we use native vim.lsp); render chadrc.lua from an embedded template with the selected theme's NvChadTheme; write five static Lua asset files (arduino.lua, clangd.lua, lspconfig.lua, dap.lua, mappings.lua); clear NvChad's base46 theme cache.
  • renode: AUR-install renode-bin (via AURHelper); pacman-install arm-none-eabi-gdb; render and write ~/.local/bin/arduino-sim from an embedded bash template driven by a platform map in config; download wokwi-cli from a version-pinned URL into ~/.local/bin/wokwi-cli (mode 0755).
  • Both modules' artifacts (Lua files, bash helper) live as real files under internal/modules/<name>/assets/ and are bundled via go:embed. No sudo required at runtime; everything lands in user-owned paths.
  • One new platform helper, platform.WriteUserFile(path, data, mode), consolidates the repeated os.MkdirAll + os.WriteFile pattern and does an atomic tmp+rename write. Plan 3a modules are not retrofitted — they stay as-is.

Tech Stack

Go stdlib only for the new helper and modules (embed, text/template, os, path/filepath). Relies on all Plan 3a platform helpers (pacman, AUR, download, extract, git) and on internal/module / internal/engine / internal/tui from Plans 1-2.

Files

New — platform helper

  • internal/platform/writefile.go + writefile_test.go
    • WriteUserFile(path string, data []byte, mode os.FileMode) error
    • Creates parent directory (0755) if missing, writes to path+".tmp", renames to path.
    • Tests: happy path (content + mode), missing parent dir (creates it), overwrite preserves atomicity (crash-proof against partial writes is out of scope; atomic rename is sufficient).

New — modules

  • internal/modules/neovim/neovim.go + neovim_test.go + assets/

    • assets/chadrc.lua.tmpl — text/template with {{.NvChadTheme}}.
    • assets/arduino.lua, assets/clangd.lua, assets/lspconfig.lua, assets/dap.lua, assets/mappings.lua — static, copied verbatim from the bash heredocs in lib/neovim.sh.
    • Constructor: New(cfg config.NeovimConfig, themeID string, themes []tui.Theme, pm platform.PackageManager, git platform.GitClient) Module.
    • State: NvimInstalled, StarterCloned, ChadrcPresent, BackupNeeded bool, MissingAssets []string, ThemeName string (resolved at Detect time).
  • internal/modules/renode/renode.go + renode_test.go + assets/

    • assets/arduino-sim.tmpl — text/template with {{range .Platforms}}...{{end}} rendering the case-statement platform map from config.
    • Constructor: New(cfg config.RenodeConfig, pm platform.PackageManager, aur platform.AURHelper) Module.
    • State: RenodeInstalled, GdbInstalled, SimScriptCurrent, WokwiInstalled bool.

Modified

  • internal/config/config.go + internal/config/defaults.yaml

    • type NeovimConfig struct { NvChadStarterRepo string }
    • type RenodePlatform struct { Name, ReplPath, Machine string }
    • type RenodeConfig struct { WokwiVersion, WokwiURL string; Platforms []RenodePlatform }
    • Config gains Neovim NeovimConfig and Renode RenodeConfig.
    • merge gains overlay-replace clauses for both (matching Plan 3a's pattern).
    • defaults.yaml populated with NvChad starter URL, pinned wokwi version/URL, and the three platform entries from lib/renode.sh.
  • internal/tui (wherever BuiltinThemes is declared — likely internal/tui/state.go or a themes file)

    • Theme gains NvChadTheme string. Populate for each builtin entry (one-line edits per theme).
    • Helper Theme(themes []Theme, id string) (Theme, bool) for the neovim module to resolve its themeID. (If an equivalent helper already exists, reuse it.)
  • cmd/archduino/main.go — buildRegistry gains a selectedThemeID string parameter; registers neovim and renode. TUI path: call buildRegistry(cfg, tui.DefaultThemeID()) before the wizard, then after the wizard returns with a theme selection, call it again with the selected ID before invoking the dashboard. Plain-output path: pass cfg's default theme directly.

Module Contract Details

Both modules use the Plan 3a archetype pattern:

  • Detect — pure reads: PackageManager.IsInstalled, os.Stat, byte-compare of rendered/static asset files against their destination paths. For neovim, ~/.config/nvim/lua/chadrc.lua acts as the NvChad-marker; any lua/ dir without chadrc triggers BackupNeeded.
  • Plan — emit only missing actions; empty plan = already converged (idempotent).
  • Apply — sequential actions with Reporter.Progress; no action is retried.
  • Verify — re-run Detect; every boolean must be true and MissingAssets/related slices must be empty.

Error semantics unchanged from Plan 3a — engine's --halt-on-error and sibling-continuation rules apply.

Theme Propagation

The wizard's ThemeID is captured into tui.WizardState.ThemeID as before. buildRegistry takes the themeID as a second parameter; for the TUI install path, the registry is rebuilt after the wizard confirms selection and before the dashboard runs. For the plain-output path (--no-tui / --dry-run), the default theme ID is used. The neovim module resolves the ID to a tui.Theme at construction and stores the NvChadTheme for Detect/Plan. If an unknown ID is passed, neovim falls back to the first builtin theme's NvChad name (no hard error — wizards shouldn't crash the installer on a config bug).

Testing Strategy

neovim (all unit, tempdir-based)

  • TestDetectFreshSystem — no nvim, no config → plans pacman install + clone starter + strip lspconfig + write assets.
  • TestDetectExistingNvChad — chadrc.lua present → no backup planned; missing-assets test still exercises Plan.
  • TestDetectNonNvChadTriggersBackup — lua/ with no chadrc → BackupNeeded=true; Plan includes a rename-to-bak action.
  • TestPlanApplyInstallsEverything — fake Runner + fake GitClient + tempdir HOME; asserts pacman call, git clone call, backup done, assets written with expected content, chadrc contains the theme name.
  • TestPlanAfterApplyIsEmpty — idempotency.
  • TestUnknownThemeFallsBackToFirst — construction with a bogus ID still produces a valid chadrc.

renode (all unit)

  • TestDetectNothingInstalled — plans AUR install + pacman install + sim-script write + wokwi download.
  • TestDetectAllInstalled — empty plan.
  • TestApplyWritesSimScript — rendered script contains each configured platform's case-branch.
  • TestApplyDownloadsWokwi — httptest server serves a short payload; verify file written to ~/.local/bin/wokwi-cli with mode 0755.

platform.WriteUserFile

  • TestWriteUserFileCreatesParent — target under a non-existent dir → parent created.
  • TestWriteUserFileAtomicity — two sequential writes of different content → final content matches the second; no .tmp leaked after success.
  • TestWriteUserFileErrorCleanup — a failed write leaves no .tmp turd (simulate via read-only parent or unwritable tmp; accept either form if OS makes it awkward).

No integration tests against real pacman, yay, neovim, renode, or GitHub; that's Plan 4's CI.

Non-Goals

  • scaffold module — deferred to Plan 4 as native Go subcommands (archduino new, archduino theme).
  • Upstream Neovim tarball fallback (/opt/nvim-linux-x86_64) — pacman is sufficient; version-drift concerns go to Plan 4's doctor.
  • LazyVim → NvChad plugin salvage — existing non-NvChad configs get a full backup; user re-adds plugins manually.
  • Wokwi GitHub-API "latest" lookup — version is pinned in config, overrideable via overlay.
  • Retrofitting Plan 3a modules to use WriteUserFile — opportunistic cleanup only, not a Plan 3b deliverable.
  • Retroactive theme-sync across every tool (starship, zellij, yazi, lazygit) — Plan 4's archduino theme subcommand.

Open Risks / Watchpoints

  • NvChad starter drift — if upstream changes lua/plugins/init.lua's nvim-lspconfig block format, our sed-equivalent strip-logic in Go will miss it. Mitigation: implement as "find the { / }, block containing the string neovim/nvim-lspconfig and delete it", unit-test against a fixture copy of the current starter's init.lua.
  • arduino-sim pinned to Renode's REPL paths — if Renode renames platforms/cpus/rp2040.repl, the sim script breaks. Acceptable for v1; surface via Plan 4 doctor.
  • Theme propagation race — rebuilding the registry twice around the wizard is ugly. Alternative considered (module-level SetTheme after construction) was rejected as un-idiomatic. Acceptable for 2 modules; revisit if more theme-aware modules appear.
  • Asset byte-equality idempotency — if a user edits their mappings.lua by hand, every Apply will overwrite it. Documented behavior; --force-assets flag is a Plan 4 concern.