dotcli: add dot setup folders (XDG short-name migration)

Adds a dot setup subcommand family (idempotent, re-runnable machine-setup
tasks) with a folders task that brings the 8 standard XDG user
directories under a fixed short-name convention (Desktop -> .desktop,
Documents -> doc, Downloads -> dwn, Music -> mus, Pictures -> pic,
Videos -> vid, Templates/Public -> .ignoreme), hardcoded rather than read
from ~/.config/user-dirs.dirs -- that file is a separate, manually
tracked dotfile whose values can drift or go stale, and the migration
must not depend on it being correct. The nested Pictures/Screenshots
folder is renamed to pic/screenshots in the same pass.

Content left behind in a legacy full-named folder by a fresh XDG-defaults
install -- empty or not -- is merged into its short-named replacement.
A same-named collision between a legacy folder and its target is never
overwritten: it is skipped, reported, and the legacy folder is left in
place rather than deleted while something in it could not be merged.
--dry-run previews what would move/skip without touching the filesystem.
xdg-user-dirs-update runs once afterward to notify running apps/portals.
This commit is contained in:
2026-07-06 16:19:06 -04:00
parent 540928f24f
commit 1fd8e7e773
11 changed files with 783 additions and 16 deletions

View File

@@ -19,12 +19,34 @@ rule.
## Acceptance criteria
- [ ] A legacy folder with real content (a real file, not just an empty
- [x] A legacy folder with real content (a real file, not just an empty
directory) refuses to migrate without `--yes`, prints what would have
been moved, and leaves the folder and its contents untouched
- [ ] The same legacy folder migrates successfully when `--yes` is passed
- [ ] A legacy folder containing only a stray dotfile/metadata file (e.g. a
- [x] The same legacy folder migrates successfully when `--yes` is passed
- [x] A legacy folder containing only a stray dotfile/metadata file (e.g. a
fake `.directory`) is still treated as non-empty and triggers the same
confirmation gate
- [ ] `~/.config/dot/tests/dot.fish` covers the above cases and
- [x] `~/.config/dot/tests/dot.fish` covers the above cases and
`fishtape ~/.config/dot/tests/dot.fish` passes
## Implementation Notes
- `--yes`'s actual move reuses the exact same branch shape as the existing
silent-empty path (rename `Screenshots``screenshots` when present, then
`rmdir` the legacy folder), extended to also `mv` any remaining top-level
entries into the target first. Screenshots is always moved as one atomic
unit — its individual files are never mv'd/reported separately — so a
non-empty `Screenshots` (own acceptance criterion in the parent spec) is
gated and migrated the same way a non-empty top-level file would be.
- Collision handling (no-clobber `mv -n`, reporting skipped files, leaving the
legacy folder in place on a collision) is explicitly out of scope here —
it's owned by 0008-folders-collision-handling.md, per that task's own
frontmatter/spec section. The `--yes` path added here uses a plain `mv`.
- `/review-uncommitted` flagged two minor issues, both fixed: a stale comment
claiming a helper variable was used by both the silent-empty and `--yes`
paths when it was only read by the latter, and a duplicated `find`
invocation computing the same top-level listing twice under one condition
(now computed once and reused). It also flagged the non-empty "would move"
preview listing recursively-nested files individually instead of treating
`Screenshots` as one unit like the real move does — fixed so the preview
and the actual move share the same top-level-entries list.