docs(specs): add Plan 3b install-modules (neovim, renode) design

This commit is contained in:
MDietermann
2026-04-14 09:41:15 +02:00
parent 7914b7ef51
commit 027b7a88bc

View File

@@ -0,0 +1,114 @@
# 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.