# Force Reapply Design (Plan 4b) **Date:** 2026-04-14 **Scope:** Plan 4b of the Archduino Go rewrite — add `archduino install --force` that re-runs every selected module's Apply unconditionally. ## Goal Let the user bypass the four-phase contract's idempotency guard when they explicitly want to reapply everything — typically after a manual edit, corruption, or "I'm not sure what's broken, redo it" scenario. Scope is narrow: force a full reapply of currently-selected modules. No uninstall, no revert, no selective force-by-category. ## Non-goals - No uninstall / reset / revert behavior. Modules have no `Revert` method today; adding one is a separate, much larger plan. - No selective force (`--force-configs`, `--force-packages`). The user picked "full re-everything" during brainstorming; single flag only. - No new subcommand (`archduino reinstall`). A flag on `install` is enough. - No changes to `Verify`; force affects only what `Apply` does, not how we confirm it. ## Interface change Add one method to `module.Module`: ```go // PlanForce returns a plan that reapplies the module unconditionally, // ignoring current detected state. Used by `archduino install --force`. // Implementations should produce a plan equivalent to "install from // scratch" — reinstall all packages (without pacman's --needed guard) // and rewrite all managed config files. PlanForce(ctx context.Context) (Plan, error) ``` Every existing module implements this. Two categories: ### Config-writing modules `zsh`, `neovim`, `themes`, `starship`, `shellconfig`, `zellij`, `yazi` (config bits), `arduinocli` (sketch dirs, indexes). `PlanForce` produces a plan that: 1. Reinstalls packages via `platform.Pacman.Reinstall` / `platform.Yay.Reinstall` (no `--needed`). 2. Rewrites every managed config file, overwriting existing content. 3. Re-runs any post-install steps (e.g., arduino-cli core update, zsh plugin clone). ### Package-only modules `packages`, `gittools`, `fonts`, `renode`, and the package-only portions of `starship` / `yazi`. `PlanForce` produces a plan that calls `platform.Pacman.Reinstall(ctx, pkgs)` — identical to the install step but without `--needed`, so pacman physically re-fetches and reinstalls the package even if already present. ## Platform layer additions `internal/platform/pacman.go`: ```go // Reinstall runs `pacman -S --noconfirm pkg...` without the --needed flag // so pacman unconditionally reinstalls each package. func (p Pacman) Reinstall(ctx context.Context, pkgs []string) error ``` `internal/platform/aur.go`: ```go // Reinstall runs `yay -S --noconfirm pkg...` without the --needed flag. func (y Yay) Reinstall(ctx context.Context, pkgs []string) error ``` Both are straight copies of the existing `Install` methods with `--needed` omitted from the command-line args. ## Engine changes `internal/engine/engine.go`: - `RunOptions` gains a field: ```go Force bool ``` - `Run` per-module path becomes: ```go var plan module.Plan if opts.Force { plan, err = mod.PlanForce(ctx) } else { cur, derr := mod.Detect(ctx) if derr != nil { /* existing error handling */ } plan, err = mod.Plan(ctx, cur) } ``` When `Force` is true, `Detect` is not called. The Apply and Verify phases are unchanged. Force does not affect DAG ordering, reporter output, or halt-on-error behavior. ## CLI changes `cmd/archduino/main.go::newInstallCmd()`: ```go var force bool cmd.Flags().BoolVar(&force, "force", false, "Reapply every selected module, ignoring current state") ``` In `RunE`, propagate to both engine and wizard: ```go runOpts.Force = force initialState.Force = force ``` ## Wizard / TUI integration `internal/tui/state.go` — `WizardState` gains: ```go Force bool ``` `internal/tui/wizard/wizard.go` — the `planCache` path branches: - When `state.Force` is true, call `PlanForce(ctx)` instead of `Detect` + `Plan`. - When a user toggles a module's selection, the cache key includes `force` so caches from non-force runs are not reused. - Header gains a persistent banner: `⚠ Force mode: all selected modules will be reapplied`. ## Data flow ``` user: archduino install --force → main.go: force=true → RunOptions.Force = true, WizardState.Force = true → wizard.Run: per-module preview uses PlanForce (skips Detect) → user confirms → engine.Run: per-module PlanForce → Apply → Verify ``` ## Error handling No new failure modes. `PlanForce` errors are handled identically to `Plan` errors: reported via the reporter, counted in the engine summary, respected by `--halt-on-error`. ## Testing **Per-module tests** — each existing `*_test.go` gains a `TestPlanForce` case. The test arranges a "satisfied" system state (mocked platform says everything is installed, configs exist), calls `PlanForce(ctx)`, and asserts the returned plan is non-empty and contains the expected reinstall/rewrite actions. This is the core behavioral contract — without this test, a module could silently "forget" to implement force. **Engine test** — `engine_test.go` gets a case using a fake module that records whether `Detect`, `Plan`, and `PlanForce` were called. Runs with `RunOptions.Force: true` and asserts `Detect` was NOT called and `PlanForce` WAS. **Platform tests** — `pacman_test.go` and `aur_test.go` each gain a `TestReinstall` that verifies the command-line args include `-S` and `--noconfirm` but do NOT include `--needed`. **No TUI test for the banner** — visual-only, covered by the existing wizard tests for preview-cache behavior which will get a new "force preview" case. ## Interaction with existing features - `--dry-run --force` prints the force plan and exits. Existing dry-run code path is unchanged. - `--only`/`--skip` respect force: force applies only to the selected set. - `--halt-on-error` respects force identically. - Theme application at end of install (Plan 4a feature) runs unchanged; force doesn't skip it. ## File touch list **Modified:** - `internal/module/module.go` — add `PlanForce` to `Module` interface - `internal/engine/engine.go` — add `RunOptions.Force`, branch in `Run` - `internal/engine/engine_test.go` — new force test - `internal/tui/state.go` — add `WizardState.Force` - `internal/tui/wizard/wizard.go` — force-aware preview cache + banner - `internal/tui/wizard/wizard_test.go` — new preview test with force - `internal/platform/pacman.go` — add `Reinstall` method - `internal/platform/pacman_test.go` — new test - `internal/platform/aur.go` — add `Reinstall` method - `internal/platform/aur_test.go` — new test - `cmd/archduino/main.go` — add `--force` flag, wire through - 13 module files (`internal/modules/*/*.go`) — implement `PlanForce` - 13 module test files — `TestPlanForce` each **New:** none.