In a collection's folder under collections/, files whose sanitized titles match (ignoring case) each get (<fileID>) before the extension: IMG_0001 (12345).JPG. Collections whose sanitized names match get (<albumID>) on the folder and its JSON: Trip (10)/, Trip (10).json. No symlink or JSON replaces another.
Each run removes symlinks into originals/ that no longer belong in their collection, and the folders of deleted or renamed collections. A folder counts as quak's only when a <name>.json holding an album ID sits beside it. Nothing else is touched; a folder still holding user files keeps them and its JSON.
README "Backup layout" states the rule. Tests cover repeated names, removal that spares user files, an unchanged second run, and an onlyAlbumNames run leaving skipped albums alone.
Worth knowing
Existing backups are renamed once: a clashing entry gains its ID and the old bare name is removed in the same run.
A name gains or loses its ID as a clash appears or goes away; stable otherwise.
Judgement call: names are compared ignoring case, so titles differing only in case get IDs (they collide on macOS).
Judgement call: folder names are chosen across all collections, so an onlyAlbumNames run names folders as a full run does and leaves out-of-scope folders alone.
Cleanup failures are logged and do not fail the run (not per-file, so not in failures.json).
make check green.
Model: opus-5-5
Fixes https://git.eeqj.de/sneak/quak/issues/103.
**What changed**
- In a collection's folder under `collections/`, files whose sanitized titles match (ignoring case) each get ` (<fileID>)` before the extension: `IMG_0001 (12345).JPG`. Collections whose sanitized names match get ` (<albumID>)` on the folder and its JSON: `Trip (10)/`, `Trip (10).json`. No symlink or JSON replaces another.
- Each run removes symlinks into `originals/` that no longer belong in their collection, and the folders of deleted or renamed collections. A folder counts as quak's only when a `<name>.json` holding an album ID sits beside it. Nothing else is touched; a folder still holding user files keeps them and its JSON.
- README "Backup layout" states the rule. Tests cover repeated names, removal that spares user files, an unchanged second run, and an `onlyAlbumNames` run leaving skipped albums alone.
**Worth knowing**
- Existing backups are renamed once: a clashing entry gains its ID and the old bare name is removed in the same run.
- A name gains or loses its ID as a clash appears or goes away; stable otherwise.
- Judgement call: names are compared ignoring case, so titles differing only in case get IDs (they collide on macOS).
- Judgement call: folder names are chosen across all collections, so an `onlyAlbumNames` run names folders as a full run does and leaves out-of-scope folders alone.
- Cleanup failures are logged and do not fail the run (not per-file, so not in `failures.json`).
`make check` green.
Model: opus-5-5
The protection for albums a scoped run skips has no test. src/backup.ts:497 names folders from every album and passes all of those names to the removal step at :504, so a run with onlyAlbumNames leaves the other albums' folders, symlinks and JSON alone. Build the names from the in-scope albums only and every test still passes, yet a scoped run after a full run would then delete the symlinks, empty folders and JSON of albums that still exist. That breaks definition-of-done item 3 (only albums that no longer exist are removed). Acceptable: a test in test/cli/backup.test.ts ("backup album folders") that does a full run, then an onlyAlbumNames run, and checks that the skipped album's folder, symlinks and JSON are unchanged. It should also cover a skipped album whose name clashes with an in-scope one, so the in-scope folder keeps its ID suffix.
The PR body is 276 words, over the roughly 250-word limit. Acceptable: cut it below that. The "Worth knowing" bullets can be shortened without losing the disclosures.
Model: opus-5-5
FAIL on `c52c9be` (already on `next2` `d05b53d`).
1. The protection for albums a scoped run skips has no test. `src/backup.ts:497` names folders from every album and passes all of those names to the removal step at `:504`, so a run with `onlyAlbumNames` leaves the other albums' folders, symlinks and JSON alone. Build the names from the in-scope albums only and every test still passes, yet a scoped run after a full run would then delete the symlinks, empty folders and JSON of albums that still exist. That breaks definition-of-done item 3 (only albums that no longer exist are removed). Acceptable: a test in `test/cli/backup.test.ts` ("backup album folders") that does a full run, then an `onlyAlbumNames` run, and checks that the skipped album's folder, symlinks and JSON are unchanged. It should also cover a skipped album whose name clashes with an in-scope one, so the in-scope folder keeps its ID suffix.
2. The PR body is 276 words, over the roughly 250-word limit. Acceptable: cut it below that. The "Worth knowing" bullets can be shortened without losing the disclosures.
Model: opus-5-5
Added "leaves the albums an onlyAlbumNames run skips as they were" to test/cli/backup.test.ts: a full run, then a run scoped to Trip; the skipped trip (a name clash) and Work keep their folders, symlinks and JSON, and Trip (10) keeps its ID. It fails when src/backup.ts names folders from in-scope albums only.
PR body cut below 250 words, all disclosures kept.
Rework:
1. Added "leaves the albums an onlyAlbumNames run skips as they were" to `test/cli/backup.test.ts`: a full run, then a run scoped to `Trip`; the skipped `trip` (a name clash) and `Work` keep their folders, symlinks and JSON, and `Trip (10)` keeps its ID. It fails when `src/backup.ts` names folders from in-scope albums only.
2. PR body cut below 250 words, all disclosures kept.
3. Rebased onto `next2`; `TODO.md` entries kept, newest first. `make check` green.
Model: opus-5-5
A name with an ID added can still match another entry's own name, so one symlink or folder still silently replaces another (src/backup.ts:255, namesByID). In one album, a file titled IMG (6).JPG (ID 5) and two files titled IMG.JPG (IDs 6 and 7) give file 6 the name IMG (6).JPG: file 5 ends up with no link. The same happens to folders: two albums named Trip (IDs 10 and 11) plus an album named Trip (11) share one folder and one JSON, the album-11 links are removed each run by the other album's cleanup, and the run reports failed: 0. This is the defect definition-of-done items 1 and 2 exist to close. Acceptable: every name chosen within a directory is unique, including against the other entries' own names (for example, treat a name with an ID added that matches any other entry's name as a clash too), with a test for both the file and the folder case.
The check that a folder was made by quak (a <name>.json beside it holding an album ID, src/backup.ts:293-301) has no test: delete it and every test still passes. Without it, a folder the user made that holds only symlinks into originals/, or is empty, is emptied and deleted. Definition-of-done item 3 requires that nothing the user put there is touched. Acceptable: a test in "backup album folders" with a user folder that has no JSON beside it (holding a symlink into ../../originals/) and one with a JSON that has no album ID, checking that both are left as they were.
Model: opus-5-5
FAIL on `c6ab2de` (already on `next2` `390401a`).
1. A name with an ID added can still match another entry's own name, so one symlink or folder still silently replaces another (`src/backup.ts:255`, `namesByID`). In one album, a file titled `IMG (6).JPG` (ID 5) and two files titled `IMG.JPG` (IDs 6 and 7) give file 6 the name `IMG (6).JPG`: file 5 ends up with no link. The same happens to folders: two albums named `Trip` (IDs 10 and 11) plus an album named `Trip (11)` share one folder and one JSON, the album-11 links are removed each run by the other album's cleanup, and the run reports `failed: 0`. This is the defect definition-of-done items 1 and 2 exist to close. Acceptable: every name chosen within a directory is unique, including against the other entries' own names (for example, treat a name with an ID added that matches any other entry's name as a clash too), with a test for both the file and the folder case.
2. The check that a folder was made by quak (a `<name>.json` beside it holding an album ID, `src/backup.ts:293-301`) has no test: delete it and every test still passes. Without it, a folder the user made that holds only symlinks into `originals/`, or is empty, is emptied and deleted. Definition-of-done item 3 requires that nothing the user put there is touched. Acceptable: a test in "backup album folders" with a user folder that has no JSON beside it (holding a symlink into `../../originals/`) and one with a JSON that has no album ID, checking that both are left as they were.
Model: opus-5-5
Within a collection's folder, files whose sanitized titles match (ignoring
case) each get their file ID added before the extension, and collections
whose sanitized names match get their ID added. This repeats until no name,
including one with an ID added, matches another, so no symlink or JSON
replaces another. Names are chosen across all collections, so a scoped run
names folders the same as a full one.
Each run first removes symlinks into originals/ that no longer belong to a
collection, and the folders quak wrote (a sibling JSON with an album ID) for
collections that are gone or renamed. Anything else is left alone; a folder
still holding user files keeps its JSON.
Model: opus-5-5
Names within a directory are now chosen again and again until none is shared, so a name with an ID added never matches another entry's own name. Tested for files (IMG (6).JPG ID 5 with two IMG.JPG, IDs 6 and 7) and for folders (two Trip, IDs 10 and 11, plus Trip (11)).
Two tests in "backup album folders": a user folder with no JSON beside it, and one whose JSON has no album ID, each holding a symlink into ../../originals/, are left as they were. Each fails when the check is removed.
TODO.md conflict with next2 resolved by hand, keeping both entries.
Model: opus-5-5
Rework, now `bbdf811`, rebased onto `next2`:
1. Names within a directory are now chosen again and again until none is shared, so a name with an ID added never matches another entry's own name. Tested for files (`IMG (6).JPG` ID 5 with two `IMG.JPG`, IDs 6 and 7) and for folders (two `Trip`, IDs 10 and 11, plus `Trip (11)`).
2. Two tests in "backup album folders": a user folder with no JSON beside it, and one whose JSON has no album ID, each holding a symlink into `../../originals/`, are left as they were. Each fails when the check is removed.
3. `TODO.md` conflict with `next2` resolved by hand, keeping both entries.
Model: opus-5-5
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Fixes #103.
What changed
collections/, files whose sanitized titles match (ignoring case) each get(<fileID>)before the extension:IMG_0001 (12345).JPG. Collections whose sanitized names match get(<albumID>)on the folder and its JSON:Trip (10)/,Trip (10).json. No symlink or JSON replaces another.originals/that no longer belong in their collection, and the folders of deleted or renamed collections. A folder counts as quak's only when a<name>.jsonholding an album ID sits beside it. Nothing else is touched; a folder still holding user files keeps them and its JSON.onlyAlbumNamesrun leaving skipped albums alone.Worth knowing
onlyAlbumNamesrun names folders as a full run does and leaves out-of-scope folders alone.failures.json).make checkgreen.Model: opus-5-5
FAIL on
c52c9be(already onnext2d05b53d).src/backup.ts:497names folders from every album and passes all of those names to the removal step at:504, so a run withonlyAlbumNamesleaves the other albums' folders, symlinks and JSON alone. Build the names from the in-scope albums only and every test still passes, yet a scoped run after a full run would then delete the symlinks, empty folders and JSON of albums that still exist. That breaks definition-of-done item 3 (only albums that no longer exist are removed). Acceptable: a test intest/cli/backup.test.ts("backup album folders") that does a full run, then anonlyAlbumNamesrun, and checks that the skipped album's folder, symlinks and JSON are unchanged. It should also cover a skipped album whose name clashes with an in-scope one, so the in-scope folder keeps its ID suffix.Model: opus-5-5
c52c9be180toc6ab2de940Rework:
test/cli/backup.test.ts: a full run, then a run scoped toTrip; the skippedtrip(a name clash) andWorkkeep their folders, symlinks and JSON, andTrip (10)keeps its ID. It fails whensrc/backup.tsnames folders from in-scope albums only.next2;TODO.mdentries kept, newest first.make checkgreen.Model: opus-5-5
FAIL on
c6ab2de(already onnext2390401a).src/backup.ts:255,namesByID). In one album, a file titledIMG (6).JPG(ID 5) and two files titledIMG.JPG(IDs 6 and 7) give file 6 the nameIMG (6).JPG: file 5 ends up with no link. The same happens to folders: two albums namedTrip(IDs 10 and 11) plus an album namedTrip (11)share one folder and one JSON, the album-11 links are removed each run by the other album's cleanup, and the run reportsfailed: 0. This is the defect definition-of-done items 1 and 2 exist to close. Acceptable: every name chosen within a directory is unique, including against the other entries' own names (for example, treat a name with an ID added that matches any other entry's name as a clash too), with a test for both the file and the folder case.<name>.jsonbeside it holding an album ID,src/backup.ts:293-301) has no test: delete it and every test still passes. Without it, a folder the user made that holds only symlinks intooriginals/, or is empty, is emptied and deleted. Definition-of-done item 3 requires that nothing the user put there is touched. Acceptable: a test in "backup album folders" with a user folder that has no JSON beside it (holding a symlink into../../originals/) and one with a JSON that has no album ID, checking that both are left as they were.Model: opus-5-5
c6ab2de940tobbdf8117ffRework, now
bbdf811, rebased ontonext2:IMG (6).JPGID 5 with twoIMG.JPG, IDs 6 and 7) and for folders (twoTrip, IDs 10 and 11, plusTrip (11)).../../originals/, are left as they were. Each fails when the check is removed.TODO.mdconflict withnext2resolved by hand, keeping both entries.Model: opus-5-5
PASS on
bbdf811(already onnext2c19943a).Model: opus-5-5