Appearance
Windows PowerShell Lane Refactor Plan
Status: draft for further broadening.
Summary
Refactor the Windows lane around three locked decisions:
- PowerShell profiles must not live in OneDrive. Use Scoop-owned PowerShell's
$PSHOME/persist profile files as the managed loader, and remove only repo-owned OneDrive CurrentUser stubs. - Use normal interactive
mise activate pwsh, not--shims, with Scoop owning themisebinary and mise owning versioned developer and prompt tools. - Replace the current group-first
windows/scoop/apps.jsonmodel with split, tool-first JSON inventory files that drive Windows bootstrap, upgrade, docs, and generated mise TOML. - Add a lane-aware daily update workflow that can be run from any directory and can cheaply prompt when the host has not checked updates recently.
- Add managed DNS as an opt-in personal-machine baseline that applies during bootstrap when the machine profile declares it.
- Add a personal desktop baseline for selected OS settings beyond classic dotfiles: file manager visibility/view mode, clipboard history, default app posture, 1Password GUI install, repo-managed startup entries, power settings, and search indexing exclusions.
Key Changes
PowerShell Startup
- Add managed Windows-only files for
~/scoop/persist/pwsh/profile.ps1and~/scoop/persist/pwsh/Microsoft.PowerShell_profile.ps1; both load~/.config/powershell/profile.ps1through the existing global guard. - Stop installing CurrentUser
$PROFILEstubs under Documents/OneDrive. Replace the current profile installer script with a cleanup script that:- deletes
$PROFILE.CurrentUserAllHostsand$PROFILE.CurrentUserCurrentHostonly when they exactly match the old repo stub, - writes timestamped backups for non-managed content,
- warns when custom CurrentUser profile content remains.
- deletes
- Keep Windows Terminal and Zed on ordinary
pwsh -NoLogo; do not use launcher-only profile loading as the primary path. - Update docs and decisions to state that Scoop
pwshis the Windows profile host and OneDrive-backed CurrentUser profiles are not repo-managed.
Mise And Prompt Performance
- Change PowerShell startup from
mise activate -q --shims pwshto normalmise activate -q pwsh; never mix full activation and--shimsin the profile. - Keep Scoop as owner of the
miseexecutable; forbidmiseas a mise-managed global tool in validation. - Remove manual PATH priority that prepends mise shims. Preserve
~/.local/bin, Scoopmise, Git for Windows, Scooppwsh, and Scoop shims, while letting full mise activation expose active tool install bins. - Set
MISE_BASH_PATHto standard Git for Windows Bash before activation and setMISE_NOT_FOUND_AUTO_INSTALL=falsein the managed PowerShell profile unless a local override changes it. - Add a Windows
dotfiles-refresh-powershell-initscript that resolves real tool binaries throughmise which, generates cached snippets forzoxide,starship, andatuin, validates/parses them, and atomically updates~/.cache/dotfiles/powershell/init.d. - Make normal profile startup source cached snippets only. Failed or missing snippet generation must not block prompt display.
- Add a Windows
just refresh-shell-cacherecipe and call the refresh script after Windows mise install/upgrade paths.
Daily Update Workflow
- Add one repo-owned command,
dotfiles-update, available in every lane and safe to run from any directory. It must locate the dotfiles source throughchezmoi source-pathfirst, then a documented local override if needed. - Support three modes:
dotfiles-update preview: show pending updates across the lane's package managers without applying them.dotfiles-update apply: update all repo-managed unpinned tools and apps across the lane's package managers, then refresh shell caches and run the lane's fast smoke check.dotfiles-update maybe: perform no network or package-manager work; read the local update timestamp and print a concise prompt only when the last successful preview/apply is older than 24 hours.
- Store update state under
${XDG_STATE_HOME:-~/.local/state}/dotfileson Unix-like lanes and~\.local\state\dotfileson native Windows. - Wire the shell startup prompt as a cheap state-file check only. Shell startup must never run package-manager, network,
mise outdated, orwingetwork. - Reuse the existing lane upgrade scripts as the execution backend rather than creating a second updater. The daily command is the user-facing wrapper around preview/apply behavior.
- Treat Codex and other high-churn tools as eager-latest mise tools unless the inventory marks them pinned or blocked.
Managed DNS Baseline
- Add DNS machine config, for example:
dns.manage = truedns.provider = "cloudflare"- optional lane-specific adapter or connection allowlists.
- Treat DNS as part of this personal workstation baseline when enabled. It is not silent generic-dotfiles behavior; it only applies when the profile opts in.
- Define Cloudflare provider data in repo source:
- IPv4:
1.1.1.1,1.0.0.1 - IPv6:
2606:4700:4700::1111,2606:4700:4700::1001
- IPv4:
- Windows behavior:
- apply only to selected active physical adapters by default;
- skip VPN, virtual, Hyper-V, WSL, Docker, loopback, tunnel, and disconnected adapters unless explicitly configured;
- reset must restore DHCP/automatic DNS with
Set-DnsClientServerAddress -ResetServerAddresses; - missing admin rights must fail cleanly and print the exact elevated command.
- WSL behavior:
- do not independently manage
/etc/resolv.confby default; - prefer generated/inherited/tunnelled Windows DNS;
- provide status and doctor guidance when WSL DNS does not reflect the Windows baseline.
- do not independently manage
- CachyOS/Linux behavior:
- use NetworkManager connection-profile DNS settings when NetworkManager owns the active connection;
- reset must restore automatic DNS for the managed connection;
- if NetworkManager is absent or does not own the connection, report unsupported/manual rather than guessing.
- DNS changes must be idempotent. If current DNS already matches the configured provider, no-op.
- Validate target selection before changing anything. If verification fails after a change, attempt to reset/restore the affected target and report the exact remaining state.
Personal Desktop Baseline
- Keep personal desktop baseline configuration in the machine-specific chezmoi data, not a separate config file. The source template remains
home/.chezmoi.toml.tmpl, and each host's effective config remains~/.config/chezmoi/chezmoi.toml. - Add baseline config, for example:
desktop_baseline.manage = truedesktop_baseline.browser = "chrome"desktop_baseline.browser_backup = "firefox"- per-area booleans for
file_manager,clipboard_history,default_apps,password_manager,startup_entries,power, andsearch_index.
- File manager baseline:
- Windows: show file extensions, show hidden files, and set Explorer's default folder view to details where stable registry-backed settings exist.
- CachyOS KDE: manage Dolphin to show hidden files and use details view if the relevant
dolphinrckeys are stable; otherwise report drift through doctor. - WSL: no GUI file-manager mutation by default.
- Clipboard history:
- Windows: enable clipboard history when configured.
- CachyOS/Linux: status-only for now unless a specific KDE clipboard-history setting proves stable.
- WSL: no independent clipboard-history management.
- Default apps:
- Set Chrome as the preferred browser and Firefox as backup in repo data.
- CachyOS: apply browser, editor, file manager, PDF, text, and URL handler defaults through
mimeapps.list; update the current Firefox default to Chrome when Chrome is installed. - Windows: install browsers and report default-app drift. Use SetUserFTA for repo-enabled current-user associations when the tool is installed and explicitly configured. Do not hand-write protected
UserChoiceregistry hash data. Open the relevant Settings page and print manual instructions when Windows blocks reliable mutation.
- Browser baseline:
- Install Chrome as the primary browser and Firefox as backup on Windows and CachyOS.
- Do not manage browser extensions; browser profiles and sync own extension state.
- Password manager:
- Windows: winget owns 1Password GUI via
AgileBits.1Password; keep 1Password CLI as a separate CLI tool where needed. - CachyOS: use the Arch/AUR
1passwordpackage when an AUR helper is available; fall back to documented manual install if no AUR helper exists. - WSL: do not install a separate Linux GUI 1Password app. Keep WSL focused on CLI/status guidance and use the Windows GUI app as the desktop app.
- Never configure vaults, accounts, credentials, or browser extension sign-in.
- Windows: winget owns 1Password GUI via
- Startup app allow/deny:
- Manage only startup entries created by this repo.
doctormay show independent startup entries in a separate informational section, but must not mutate them.- Provide preview before enabling/disabling any repo-managed startup entry.
- Power settings:
- Manage only explicit machine-profile settings such as AC/battery sleep, display timeout, hibernate, and lid behavior.
- Windows uses
powercfg/PowerShell helpers; CachyOS uses KDE/systemd/logind settings only where the owner is clear. - Missing elevation must fail cleanly with the exact rerun command.
- Search indexing exclusions:
- Windows: exclude known high-churn repo/dev/cache paths such as code roots,
.git,node_modules, mise install/cache paths, Codex cache/session/log paths, and other configured paths when Windows Search indexing is present. - CachyOS KDE: manage Baloo exclusions only if Baloo is active and config keys are stable; otherwise report guidance through doctor.
- WSL: no independent search-index management.
- Windows: exclude known high-churn repo/dev/cache paths such as code roots,
- Backup/sync clients remain out of scope for automation in this phase. The repo may install a client later, but account sign-in, selected folders, bandwidth policy, and cloud credentials stay manual unless a concrete client has safe declarative config.
Windows Default Apps And Open With
- Keep this as a Windows machine-baseline module, not normal shell/dotfile startup. Proposed source layout:
windows/default-apps/README.mdwindows/default-apps/associations.jsonwindows/default-apps/open-with.jsonscripts/windows/default-apps.ps1scripts/windows/open-with.ps1
- Recommended implementation strategy:
- start with read-only
status,export, anddoctorcommands before any mutation; - add allowlisted app registration/Open With cleanup next, because that is ordinary current-user registry state and does not need
UserChoicehash writes; - add SetUserFTA-backed default association apply/reset only after target apps are installed, registered, backed up, and discoverable;
- keep browser defaults as the last apply path because
http,https,.htm, and.htmlare the most likely to be reset or blocked by Windows; - treat missing SetUserFTA, missing ProgIDs, and missing apps as warnings in bootstrap and hard failures only for direct
default-apps apply --strict.
- start with read-only
- Recommended initial target policy:
- browser: Chrome primary, Firefox backup;
- PDF: SumatraPDF;
- archives: 7-Zip;
- images: IrfanView with Paint/Firefox/Chrome allowed in Open With;
- media: VLC, with mpv available but not necessarily default;
- text/dev/config/Markdown: configurable editor target, with Zed preferred when it has a valid ProgID and VS Code/Notepad++ as Open With fallbacks.
- Model Windows association mechanisms explicitly:
UserChoiceis the protected per-user default association. Read it for status, but never writeProgIdorHashmanually.ProgIDnames the registered handler that SetUserFTA or Windows Settings can select.OpenWithProgidsis the preferred per-extension way to make supported handlers appear in Open With.OpenWithListis legacy MRU/noise state. Clean stale entries only when they point to missing repo-managed apps.Applications\<exe>registers a desktop app executable, friendly name, icon, and open command for Open With.SupportedTypesandNoOpenWithcan constrain noisy apps, but only for repo-owned registrations.App Pathshelps ShellExecute resolve real executables without relying on PATH; use current-userApp Pathsfor repo-owned portable/Scoop GUI apps where needed.- DISM default-app XML is useful for export/reference and future image or first-logon work, but it is not the primary current-user mutation path.
- Use SetUserFTA as the default association backend when
default_apps.manage = trueanddefault_apps.tool = "setuserfta".- Store SetUserFTA as a repo-managed direct/manual Windows tool until its licensing/distribution story is explicit.
- Generate a temporary SetUserFTA config from repo JSON and apply it in one invocation.
- Use SetUserFTA
get,query,find,browser,-cmdline, and-validonlyfor discovery and verification. - Use SetUserFTA browser mode for Chrome/Firefox where it resolves a stable browser registration; otherwise set
http,https,.htm, and.htmlby discovered ProgID. - Use SetUserFTA
delonly as an explicit reset action for configured associations. Warn that protected types such as.pdf, media/image types, and browser protocols may reset to Windows defaults after deletion.
- Discover valid ProgIDs after app install, before applying defaults:
- query SetUserFTA for current/default associations;
- enumerate
HKCUandHKLMSoftware\RegisteredApplicationscapability entries for browsers and GUI apps; - inspect
HKCU\Software\Classes,HKLM\Software\Classes, andApplications\<exe>for configured app handlers; - read repo inventory metadata first when a Scoop/winget package has known ProgIDs.
- Export known-good state through two channels:
- SetUserFTA export for the selected extension/protocol list;
- DISM
/Online /Export-DefaultAppAssociationsas a reference snapshot only. Keep host exports under local state/cache by default, not committed, unless manually curated intowindows/default-apps/associations.json.
- Apply order:
- install/register target apps first;
- backup current selected associations and repo-owned Open With registry keys;
- validate that requested ProgIDs exist;
- apply available SetUserFTA associations;
- apply repo-owned Open With registration/cleanup;
- verify and report drift without hard-failing missing apps.
- Scoop registry notes are not automatically honored today. The current Windows bootstrap only runs
scoop install/scoop update; it does not parse or import post-install registry note files such as VS Codeinstall-associations.reg, Notepad++install-context.reg, 7-Zip shell integration, PowerShell context entries, Windows Terminal context entries, Python PEP 514, or Tailscale startup notes. - Do not blindly import every Scoop-generated
.regfile. Add explicit tool-inventory metadata for allowed registry integrations:context_menu: import generated context-menu registration for Code, Notepad++, Windows Terminal, PowerShell, and 7-Zip only when enabled.associations: import registration-only files that create ProgIDs andOpenWithProgids, but do not set protectedUserChoicedefaults.startup: managed separately by the startup allow/deny module.pep514: Python registration is disabled unless a Windows-native Python owner is intentionally added back to the inventory.blocked: OneCommander file-manager takeover, global shell hijacks, and broad machine-wide imports stay manual.
- Open With cleanup is current-user and repo-scoped:
- register approved Scoop GUI apps under
HKCU\Software\Classes\Applications\<exe>using realcurrentapp paths; - add configured extension links under
OpenWithProgids; - remove dead
Applications\<exe>entries and per-extensionOpenWithList/OpenWithProgidsreferences only when they match repo-managed apps whose executable no longer exists; - optionally add
SupportedTypesfor repo-registered apps to reduce noisy suggestions; - avoid mutating independent user-installed apps.
- register approved Scoop GUI apps under
- The existing local
C:\Users\alvaro\Fix-OpenWithScoop.ps1is a useful prototype for dry-run cleanup, dead application detection, and Scoop shortcut registration, but the repo implementation should be driven by explicit config instead of a broad shortcut scan. - Current observed Windows drift to address in this module:
http,https, and.htmlcurrently point at Firefox, while the desired baseline is Chrome-first;- some Scoop registry note effects are already present on the machine outside repo ownership, including Code, Notepad++, and 7-Zip context/Open With registration. The module should make that state declarative and reversible.
Tool Inventory Refactor
- Create split JSON inventory files under
tools/, each with atoolsobject keyed by canonical tool name. - Use this schema shape for every entry:
- common metadata:
description,tags, optionalnotes; - per-lane metadata:
windows,wsl,cachyos; - per-lane fields:
owner,package,version,groups,bucket,install,upgrade,detect,state,update_policy.
- common metadata:
- Owners must be explicit:
scoop,winget,direct,mise,apt,pacman,aur,manual, orabsent. - Update policies must be explicit where the default is not enough:
latest,pinned,manual,blocked, orabsent. - Delete
windows/scoop/apps.jsonas an obsolete source path. Update bootstrap, upgrade, smoke tests, and docs to read the new tool-first JSON directly. - Generate or validate mise TOML from the JSON inventory so
home/dot_config/mise/conf.d/*.tomlis treated as derived, not hand-authored source. - Encode Windows bootstrap groups as metadata on tool entries:
bootstrap_auth,core,runtime_build,gui,fonts, andoptional. - Encode winget/direct special cases in the same inventory: Git for Windows, Visual Studio Build Tools, Zed, and Logi Options+ offline.
- Encode Windows registration metadata on tool entries when needed:
progids,app_paths,open_with_exe,scoop_registry_notes,context_menu,association_reg_files,startup_reg_files, andregistration_policy.registration_policymust be one ofapply,preview,manual,blocked, orabsent.
Interfaces
scripts/bootstrap-windows.ps1keeps the existing bootstrap intent but reads tool inventory JSON. Remove-RepairMiseShims; bootstrap should install required groups and refresh PowerShell init cache when mise/tooling is available.- Windows bootstrap should run the default-app/Open With module only after GUI apps are installed and only when
desktop_baseline.default_appsoropen_with.manageis enabled. Missing SetUserFTA or missing target ProgIDs warn and continue; they must not break core bootstrap. scripts/upgrade-windows.ps1reads the same inventory for managed Scoop/winget upgrades and de-owned/absent Scoop apps.- Add cross-lane
dotfiles-updatelaunchers under~/.local/bin:- Unix-like lanes use an executable shell helper.
- Native Windows uses
.cmdand.ps1wrappers. - All wrappers delegate to lane-specific preview/apply scripts and update the shared timestamp only after successful preview/apply.
- Add cross-lane
dotfiles-dnslaunchers under~/.local/binwith:dotfiles-dns statusdotfiles-dns previewdotfiles-dns setdotfiles-dns resetdotfiles-dns doctor
- Bootstrap scripts must call
dotfiles-dns setwhen DNS management is enabled in the machine profile. If elevation is missing, bootstrap should print the exact elevated command and continue with a warning;dotfiles-dns setitself should fail hard. - Add cross-lane
dotfiles-baselinelaunchers under~/.local/binwith:dotfiles-baseline statusdotfiles-baseline previewdotfiles-baseline applydotfiles-baseline reset <area>dotfiles-baseline doctor
- Add Windows-only default-app/Open With commands under
dotfiles-baseline:dotfiles-baseline default-apps statusdotfiles-baseline default-apps exportdotfiles-baseline default-apps applydotfiles-baseline default-apps resetdotfiles-baseline open-with statusdotfiles-baseline open-with backupdotfiles-baseline open-with applydotfiles-baseline open-with reset
- Bootstrap scripts should apply safe enabled baseline areas by default for opted-in personal profiles. Areas that are doctor-only in this phase must report guidance without mutating state.
- Add
scripts/validate-tool-inventory.pyandscripts/generate-tool-inventory.pyusing only Python standard library. - Add or update
justrecipes:refresh-shell-cacheworks on Windows and Unix-like lanes.mise-policy-checkalso validates generated mise TOML sync.upgraderemains the script-facing backend.checkincludes tool inventory validation.
Test Plan
- Source checks:
python3 scripts/validate-tool-inventory.pypython3 scripts/generate-tool-inventory.py --checkscripts/smoke-test.sh --source-onlyjust check
- Daily update checks:
dotfiles-update maybefrom$HOME, from the dotfiles repo, and from an unrelated directory.dotfiles-update previewin each lane where the required package managers are available.dotfiles-update applyon at least one host per lane before final rollout.- Verify pinned/manual/blocked tools are reported but not updated.
- DNS checks:
dotfiles-dns status,preview, anddoctoron each lane.- Windows: dry-run adapter selection excludes VPN, virtual, Hyper-V, WSL, Docker, loopback, tunnel, and disconnected adapters; elevated set/reset works on active physical adapters.
- WSL: status reports generated
/etc/resolv.confand inherited DNS; set does not mutate WSL DNS by default. - CachyOS: NetworkManager preview names the connection profile before mutation; set/reset works on a NetworkManager-owned connection.
- Missing admin/root privileges print exact rerun commands.
- Desktop baseline checks:
dotfiles-baseline status,preview, anddoctoron each GUI-capable lane.- Windows: Explorer hidden-file/file-extension/details-view settings are idempotent; clipboard history applies; 1Password GUI resolves from winget; startup mutation touches only repo-managed entries; search-index exclusions are previewed before apply.
- CachyOS:
mimeapps.listrenders Chrome-first with Firefox backup; Dolphin, power, and Baloo settings apply only when stable owners/keys are verified. - WSL: GUI baseline reports inherited/host-owned behavior and does not install GUI 1Password or mutate Linux GUI settings by default.
- Windows default-app drift for Chrome is reported with manual instructions if Windows blocks reliable programmatic mutation.
- Windows default-app/Open With checks:
- Fresh Windows install after GUI bootstrap:
statusreports missing SetUserFTA or missing ProgIDs as warnings;applydoes not mutate until target apps are installed and registered. - Known-good export: SetUserFTA selected association export and DISM XML reference export both complete and are written to local state/cache.
- Apply with SetUserFTA: configured unpinned/current-user associations apply for
.pdf, text/dev/config files, archives, images, media,http,https,.htm, and.html; protected types verify by readingUserChoicerather than by assuming registry writes succeeded. - Browser defaults: Chrome becomes the target for
http,https,.htm, and.htmlwhen a valid Chrome registration is discovered; Firefox remains installed as backup. - Scoop registry notes: explicitly allowlisted generated
.regfiles import cleanly after Scoop install/update, and blocked files are reported but not imported. - Open With cleanup: dead repo-managed Scoop app references are removed from
Applications\<exe>,OpenWithList, andOpenWithProgids; independent user-installed app entries are only reported. - App update/path change: registrations point to Scoop
currentpaths where safe and are refreshed afterscoop update. - Missing app: missing ProgIDs, missing
.regfiles, and missing executables warn and continue without hard failure. - Reset: SetUserFTA reset uses explicit configured
delactions only, and Open With reset restores the registry backup for repo-owned keys.
- Fresh Windows install after GUI bootstrap:
- Windows runtime checks:
.\scripts\smoke-test-windows.ps1pwsh -NoLogo -Command '$PSVersionTable.PSEdition; "profile-ok"'with a hard timeoutwhere.exe mise,where.exe starship,where.exe atuin,where.exe zoxide,where.exe bashmise activate -q pwshdotfiles-refresh-powershell-init- confirm no runaway
mise.exeorbash.exeprocesses after startup and refresh tests.
- Smoke assertions must reject:
mise activate ... --shims pwshin PowerShell sources,- mise shim directories being manually prepended,
- repo-managed CurrentUser profile stubs under OneDrive,
- stale references to
windows/scoop/apps.json. - startup code that performs network or package-manager update work.
- DNS mutation from shell startup or unmanaged profiles.
- desktop baseline mutation from shell startup.
Assumptions
- The Windows lane standardizes on Scoop-installed PowerShell 7; non-Scoop PowerShell hosts are not a compatibility target.
- Existing OneDrive CurrentUser profile files may be removed only when they exactly match the repo-owned stub; custom profile content is backed up and left for manual review.
- JSON is the canonical inventory format because PowerShell can parse it during fresh bootstrap without extra modules.
- Daily update prompting is opt-out through local state/config, but real update execution is explicit; shell startup only prints a reminder.
- DNS is managed by default only for personal profiles that opt in through repo config. Router-level DNS, encrypted DNS, DoH/DoT policy, and per-network split-DNS are out of scope for this phase.
- Windows default-app association mutation depends on SetUserFTA being present and locally acceptable to use. Without it, the repo reports drift and opens Windows Settings guidance rather than spoofing
UserChoicehashes. - Scoop-generated registry files are treated as upstream registration hints, not trusted policy. Only explicitly allowlisted current-user integrations are imported by repo code.
- Browser extensions, backup/sync account configuration, mouse acceleration, and non-repo startup entries are out of scope for mutation in this phase.