Branch: atomic-edits → main
PR 6 of the review fixes plan (docs/review-fixes-plan.md), part of the 0.5.3 series; the version does not change in this PR.
Each edit checks first, then changes the project (M3). An edit that fails now leaves the project exactly as it was, and only an edit that succeeds marks it as having unsaved changes.
AssignSample works out everything before touching the project: which pool entry to reuse, which entries replacing the pad frees, the sample limit, the new file name, and the new pad, built in full from the blank pad. Only then does it replace the pad and add the pool entry. None of those last steps can fail.
kick.wav with a different kick.wav keeps the name instead of making it kick001.wav. A new freedBy helper works this out, and also handles a file used by two layers of the same pad.SetTune checks the pad's first layer is an object before writing. Before, if it was not, the project was marked changed anyway.SetMuteGroup marks the project changed only if the write worked.SetSequence looks up and checks every clip before writing anything. Before, if a clip had no value, the new name, BPM and length were already half written when it failed. The length write is split into lengthClips (check) and writeLength (write); setLength, used by AddSequence, uses both.AddSequence and RemoveSequence set dirty after the change rather than before. RemoveSequence picks the next current sequence before removing anything.Project fields are written only by project (M4).
Dirty and Sources are now the unexported dirty and sources.Dirty() reads the flag, MarkExported() clears it after an export, and Source(file) returns a pool file's source and whether one is recorded.app and export use these. The CLI and tools did not use either field.Tests.
TestFailedEditsChangeNothing: each of these edits fails on a loaded DemoFile and leaves the encoded project, the sources and Dirty() unchanged:
Against the old ordering, the tune, mute-group and sequence cases fail.
TestAssignReplacesBrokenPad: the plan's example, a pad whose layersv is empty. AssignSample does not fail on it: it starts from the blank pad, so the broken pad is replaced whole.TestAssignReusesFreedName: the freed-name behaviour above.Sources directly now rename a pool entry in the tree, or call the name check and the zip writer directly.AssignSample replaces the pad with the blank pad before writing, so a pad with no layers is simply replaced. The failures that can actually happen are now all covered.project can record a source now, so Folder and Zip can no longer be handed a bad pool name from outside, and their check is reached only if project itself has a bug. The test calls the check and the zip writer directly. internal/export coverage is 85.9%, still above PR 3's 85%.project: export_test.go sets Name to a/b to test the export name check, since SetName refuses such a name. No non-test code outside project writes a project field. Name and SourceDir stay exported, as the plan only names Dirty and Sources.On the server, every command with GOTOOLCHAIN=go1.27.4:
go test ./... passes, including the golden and round-trip tests.go vet ./... (also with GOOS=windows) and staticcheck ./... are clean.govulncheck ./...: "No vulnerabilities found."npm install, npm run check (0 errors, 0 warnings) and npm run build pass. The lockfile was not committed. The bindings did not change.wails3 build builds, and the app starts under Xvfb.On the laptop (PowerShell, in the repo):
git fetch; git checkout atomic-editsgo test ./...: all ok.wails3 dev: load testdata\device\DemoFile.xpj. The footer shows no unsaved changes..wav. An error shows, A1 keeps its sample, and the footer still shows no unsaved changes.