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/nvimif 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'snvim-lspconfigblock (we use nativevim.lsp); renderchadrc.luafrom an embedded template with the selected theme'sNvChadTheme; 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(viaAURHelper); pacman-installarm-none-eabi-gdb; render and write~/.local/bin/arduino-simfrom an embedded bash template driven by a platform map in config; downloadwokwi-clifrom 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 viago:embed. No sudo required at runtime; everything lands in user-owned paths. - One new platform helper,
platform.WriteUserFile(path, data, mode), consolidates the repeatedos.MkdirAll + os.WriteFilepattern 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.goWriteUserFile(path string, data []byte, mode os.FileMode) error- Creates parent directory (0755) if missing, writes to
path+".tmp", renames topath. - 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/templatewith{{.NvChadTheme}}.assets/arduino.lua,assets/clangd.lua,assets/lspconfig.lua,assets/dap.lua,assets/mappings.lua— static, copied verbatim from the bash heredocs inlib/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/templatewith{{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.yamltype NeovimConfig struct { NvChadStarterRepo string }type RenodePlatform struct { Name, ReplPath, Machine string }type RenodeConfig struct { WokwiVersion, WokwiURL string; Platforms []RenodePlatform }ConfiggainsNeovim NeovimConfigandRenode RenodeConfig.mergegains overlay-replace clauses for both (matching Plan 3a's pattern).defaults.yamlpopulated with NvChad starter URL, pinned wokwi version/URL, and the three platform entries fromlib/renode.sh.
-
internal/tui(whereverBuiltinThemesis declared — likelyinternal/tui/state.goor a themes file)ThemegainsNvChadTheme 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—buildRegistrygains aselectedThemeID stringparameter; registers neovim and renode. TUI path: callbuildRegistry(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: passcfg'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.luaacts as the NvChad-marker; anylua/dir without chadrc triggersBackupNeeded. - 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-cliwith 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.tmpleaked after success.TestWriteUserFileErrorCleanup— a failed write leaves no.tmpturd (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
scaffoldmodule — 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'sdoctor. - 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 themesubcommand.
Open Risks / Watchpoints
- NvChad starter drift — if upstream changes
lua/plugins/init.lua'snvim-lspconfigblock format, our sed-equivalent strip-logic in Go will miss it. Mitigation: implement as "find the{/},block containing the stringneovim/nvim-lspconfigand delete it", unit-test against a fixture copy of the current starter'sinit.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
SetThemeafter 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.luaby hand, every Apply will overwrite it. Documented behavior;--force-assetsflag is a Plan 4 concern.