11 KiB
Problem Statement
The old ~/wrk/dotfiles repo's setup_folders (part of its bash bin/dot init) renamed the standard XDG user folders to short names (Documents→doc, Downloads→dwn, etc.) for better fish shell-completion ergonomics — shorter shared prefixes are easier to disambiguate by typing fewer characters. That behavior has no equivalent in the new bare-repo dot CLI. Right now this machine's user-dirs.dirs is untracked and has drifted from even the old convention: it uses the full XDG default names, plus an ad hoc XDG_PROJECTS_DIR=$HOME/Projects line that never existed in the old repo at all. If this machine were rebuilt today, none of the short-name convention would be restored, and the current drifted state isn't recorded anywhere.
Solution
Add a folders task to a new dot setup subcommand family (the general home for idempotent, re-runnable machine-setup tasks, as opposed to dot init's one-shot bootstrap). dot setup folders brings the 8 standard XDG user directories under the project's short-name convention, tracks the resulting user-dirs.dirs directly as a plain dotfile, and safely migrates any content sitting in the old, full-named folders into their short-named replacements.
~/wrk (already in active use, e.g. ~/wrk/dotfiles) replaces the old Projects-style folder as the general working-files location, but is treated as a plain convention-only directory, not a tracked XDG category.
User Stories
- As the machine owner, I want the standard XDG user folders renamed to short names (
doc,dwn,mus,pic,vid,.desktop), so that fish-completion on my home directory has shorter, easier-to-disambiguate shared prefixes than the full XDG default names. - As the machine owner, I want
TemplatesandPublic(both unused) collapsed into a single hidden.ignoremefolder, so that apps respectingXDG_TEMPLATES_DIR/XDG_PUBLICSHARE_DIRdon't scatter files directly into$HOME, without needing two separate unused folders. - As the machine owner, I want the nested
Pictures/Screenshotsfolder lowercased topic/screenshotsin the same pass as thePictures→picrename, so that the screenshot folder matches the rest of the short-folder naming convention without a separate migration step. - As the machine owner, I want
~/wrkto have no XDG variable pointing at it, so that a non-standard, barely-recognized XDG extension (XDG_PROJECTS_DIR) doesn't get tracked for a directory that already works fine as a plain convention. - As the machine owner, I want
user-dirs.dirstracked directly in the bare dotfiles repo like any other plain dotfile, so that the desired short names are recorded and restorable on a fresh machine without needing a code-generation step. - As the machine owner, I want
dot setup foldersto migrate content out of any legacy full-named folder into its short-named replacement automatically when the legacy folder is empty, so that re-running setup on a fresh install requires no manual folder shuffling. - As the machine owner, I want
dot setup foldersto stop and ask for explicit confirmation before moving anything out of a legacy folder that actually has content in it, so that I never silently lose files to an automated migration I forgot was going to run. - As the machine owner, I want confirmation to be satisfiable via a
--yesflag rather than an interactive prompt, so that the same command works identically whether I'm running it by hand or from an automated/tested context. - As the machine owner, I want a filename collision between a legacy folder and an already-populated short-named target to never be silently overwritten, so that re-running the migration after a partial/interrupted prior run can't destroy a file just because both sides happen to have a same-named entry.
- As the machine owner, I want to be told which files were skipped due to a collision and have the legacy folder left in place when that happens, so that I have a clear, actionable signal that something needs manual attention instead of silent partial data loss.
- As the machine owner, I want
dot setup foldersto notify running apps of the directory changes viaxdg-user-dirs-updateafter migrating, so that session-long apps pick up the new paths without requiring a full logout/login. - As the machine owner, I want to run
dot setupwith no arguments to perform every machine-setup task (folders plus future ones like extra groups) in one command, so that setting up a fresh machine doesn't require remembering and running each task individually. - As the machine owner, I want to also be able to run
dot setup folderson its own, so that I can re-run just this one task in isolation (e.g. after a confirmation was declined) without re-running unrelated setup tasks.
Implementation Decisions
- Subcommand family:
dot setup, following the project's existing nested-subcommand dispatch convention (help-then-argparse,_dot_<name>_usage). Baredot setup(no arguments) runs every machine-setup task unconditionally (folders, plus future tasks such as extra groups, mirroring the old bashbin/dot init's dual-mode: no-args ran everything, an explicit keyword ran just one task).dot setup foldersruns just the folders task. - Folder mapping (identical to the old repo's
setup_folders, no changes):Desktop→.desktop,Documents→doc,Downloads→dwn,Music→mus,Pictures→pic,Videos→vid,Templates→.ignoreme,Public→.ignoreme.TemplatesandPublicboth point at the same.ignoremefolder, as before. - Nested screenshots rename: as part of the same
Pictures→picmigration pass, the nestedScreenshotsfolder (currently created empty by KDE/Spectacle defaults) is renamed to lowercasescreenshots, so the result ispic/screenshots. This is folded into the folders task rather than deferred to the separate Spectacle-keybind work, since it's the same naming-convention concern and falls out for free oncePictures/*is moved intopic/. wrkis out of the XDG mapping: noXDG_PROJECTS_DIR(or any other XDG variable) is written for it. It's a plain, convention-only directory. The currently-existing ad hoc~/Projectsfolder (created by this machine's diverged, untrackeduser-dirs.dirs) is left alone — out of scope for the folders task, since it was never one of the 8 standard XDG categories the task manages, and it's empty and harmless.user-dirs.dirsis tracked directly as a plain dotfile in the bare repo (not generated/overwritten bydot setup foldersfrom a hardcoded table each run) — unlike KDE's rc files (tracked via a separate declarative-manifest mechanism, see thedot-kdespec),user-dirs.dirshas no volatile/machine-specific fields, so it fits the same direct-tracking treatment as any other plain dotfile (.bashrc, etc.). The tracked file is the single source of truth for the desired short names.dot setup foldersstill needs a small hardcoded table mapping each of the 8 standard XDG categories to its legacy default folder name (Documents,Downloads, etc.) — this is used purely to locate content left behind by a fresh XDG-defaults install and merge it into the already-tracked short-named target; it is not the source of truth for the target names themselves (that's the trackeduser-dirs.dirs).- Migration safety, per legacy folder:
- Empty (strict check: any file at all, including dotfiles/metadata like a stray KDE
.directoryfile, counts as non-empty) → merge silently, no prompt. - Non-empty → print what would be moved and require an explicit
--yesflag before proceeding. No interactive prompt. - Collisions (a same-named entry exists in both the legacy folder and its short-named target) → use no-clobber semantics (e.g.
mv -n) so a colliding file is never silently overwritten; report which files were skipped; leave the legacy folder in place (don't remove it) if any collision occurred, rather than deleting a folder that still holds something that couldn't be merged.
- Empty (strict check: any file at all, including dotfiles/metadata like a stray KDE
- Post-migration step: run
xdg-user-dirs-update(no arguments) once folder moves are complete, to notify running apps/portals via its D-Bus signal. This is safe against the hand-tracked file —user-dirs.dirs's own header documents that local edits are preserved across runs of the tool.
Testing Decisions
- Guiding principle: test the folders task's own logic (mapping, empty-vs-non-empty gating,
--yesbehavior, collision handling, idempotency) through the real CLI entry point, not the internals ofmv/mkdirthemselves. - Primary seam: full CLI invocation of
dot setup folders(and baredot setup), run against a scratch$HOMEper test case — the existing project convention (seedot install's tests). No new seam is introduced. - External command handling:
xdg-user-dirs-updateis faked out via aPATH-prepended fake binary that logs its invocation (and exit code), exactly mirroring howsudo/pacmanare faked fordot install's tests. Realmkdir/mv/rmdirrun for real against the scratch$HOME— no need to fake filesystem operations themselves. - Cases to cover: fresh migration of empty legacy folders (no
--yesneeded); a legacy folder with real content refuses without--yesand proceeds with it; the nestedPictures/Screenshots→pic/screenshotsrename; a stray dotfile (e.g. a fake.directory) in an otherwise-"empty" legacy folder still triggers the confirmation gate; a filename collision between legacy and target is skipped (not overwritten), reported, and leaves the legacy folder in place; re-runningdot setup foldersafter a clean migration is a no-op (idempotency); baredot setupruns the folders task as part of running everything;dot setup folders helpprints usage and touches nothing. - Prior art:
tests/dot.fish's existing scratch-$HOME-plus-fishtapepattern, and specifically the fake-sudo/fake-pacman-via-PATHtechnique used fordot install.
Out of Scope
- The extra groups task (
dot setup groupsor similar, porting the old.extra_groups/setup_usersbehavior) — it will share the samedot setupdispatcher and dual-mode (bare-runs-everything vs. named-task) shape decided here, but its own design (group list format, idempotency, etc.) was not addressed in this spec. - Any KDE-side settings (caps-lock/Escape swap, screenshot keybinds, Lock Session rebind) — covered separately by the
dot-kdespec/design. - Removing the currently-existing, now-orphaned
~/Projectsfolder — explicitly left alone, not cleaned up by this feature. - Any
~/.github/README.mdcommand-table row or~/.github/keybindings.mdupdate — not applicable here (no keybind changes), but the README row is still required by the project's standard "adding a subcommand" checklist at implementation time.
Further Notes
- The old bash
setup_folders's naivemv $from/* $tohas a latent bug this design deliberately avoids: an unquoted glob against an empty directory can misbehave, and it has no collision protection at all. The no-clobber-plus-report behavior specified here is a deliberate improvement over the old script's behavior, not a straight port. - This spec covers only the
folderstask;dot setupitself (the dispatcher,_dot_setup_usage, wiring intocommands/, the completions/help-glob duplication point noted in the project'sCLAUDE.md) needs to exist as scaffolding for this task to attach to, even though its only other planned task (extra groups) is out of scope here.