feat(macos): capture "unset" state instead of silently dropping prefs#67
Merged
Conversation
Previously `CaptureMacOSPrefs` iterated the DefaultPreferences catalog, ran `defaults read DOMAIN KEY` for each entry, and silently skipped any key the user hadn't explicitly set on their machine. Result: a snapshot that should have shown all 9 Menu Bar / Control Center toggles would only carry the 1 the user had actually customised, and the snapshot UI reported "1/1" instead of "1/9". The same dropout pattern hid most of the catalog from every category. Solution: extend the snapshot schema with an `Unset bool` flag (added with `omitempty` so legacy snapshots decode unchanged), record every catalog entry on capture — populating Value with the catalog default and Unset=true when `defaults read` fails — and have every downstream consumer treat Unset entries as informational only. - internal/snapshot/snapshot.go — Unset field on MacOSPref. - internal/snapshot/capture.go — record Unset=true instead of `continue`. - internal/cli/snapshot_import.go — filter Unset at the snapshot→RemoteMacOSPref boundary. Single chokepoint covers both install-from-snapshot (Plan → Configure) and publish (state → API); RemoteMacOSPref intentionally has no Unset field because remote config models "what should be enforced", not "user had no opinion". - internal/diff/compare.go — diffMacOS ignores Unset entries on both system and reference sides (no false-positive Missing/Changed/Extra). - internal/ui/snapshot_editor.go — Unset prefs default to unselected and carry an "(unset, default = X)" badge in their description, so the user has to opt in before publishing one. - internal/cli/snapshot.go — snapshot preview reports set/unset counts. - Tests cover JSON round-trip + legacy decode, the import filter, the diff ignore, and the editor selection default. - archtest baseline updated (pure line-number shift in compare.go from the new filter checks — same exec.Command call sites).
Codecov Report❌ Patch coverage is
📢 Thoughts on this report? Let us know! |
3 tasks
fullstackjam
added a commit
that referenced
this pull request
May 16, 2026
…68) #67 filtered Unset entries at the snapshot→state boundary and defaulted them to unselected in the TUI editor. The reasoning ("Unset means user had no opinion, don't propagate") was wrong: macOS does not write default-equal values to the plist, so common cases like "Show Sound in menu bar" — a key the user has never explicitly touched but which macOS displays by default — capture as Unset. Filtering them dropped 8 of 9 Menu Bar items from publish and the web UI showed only "1", unchecked. Treat Unset as purely informational metadata going forward: - internal/cli/snapshot_import.go — drop the boundary filter; publish every captured pref (Unset entries carry the catalog default, which is the value we'd want to enforce regardless). - internal/ui/snapshot_editor.go — default Unset entries to selected. The `(unset, default = X)` badge in the description stays, so users can see which prefs originated from the catalog default vs the user's explicit plist value, but the default action is to include them. - internal/diff/compare.go — unchanged; diffMacOS already treats Unset as "no opinion" on either side, which remains the right semantic for diff (vs publish, which is about transmitting the configured state). Tests inverted to match (and renamed so the new contract is obvious).
3 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Fixes the structural side of #66's investigation. Previously
CaptureMacOSPrefssilently dropped every catalog entry whose key wasn't explicitly set on the user's machine — so a snapshot that should have shown all 9 Menu Bar / Control Center toggles would only carry the 1 the user had actually customised, and the snapshot UI reported "1/1" instead of "1/9". Same dropout for every category.Approach
Extend
MacOSPrefwithUnset bool(omitempty for legacy snapshot compatibility), capture every catalog entry, and have every downstream consumer treat Unset entries as informational only.internal/snapshot/snapshot.goUnset boolfield withomitempty.internal/snapshot/capture.goUnset=true+ catalog default value whendefaults readfails, instead ofcontinue.internal/cli/snapshot_import.goRemoteMacOSPrefboundary. Single chokepoint covers install-from-snapshot (Plan → Configure) and publish (state → API).RemoteMacOSPrefintentionally has no Unset field.internal/diff/compare.godiffMacOSignores Unset entries on both sides — no false-positive Missing/Changed/Extra from "default" values.internal/ui/snapshot_editor.go(unset, default = X)badge in description.internal/cli/snapshot.goN (M set, K unset).internal/archtest/baseline/no-direct-exec.txtexec.Commandsites incompare.go, two slots later.Backwards compatibility
Unset bool \json:"unset,omitempty"`means snapshots written by the old CLI deserialize cleanly withUnset=false(covered byTestMacOSPref_JSON_LegacyDecodeNoUnsetField`). Newly written snapshots include the field only for unset entries, so the on-disk size barely changes.RemoteMacOSPrefschema is unchanged — Unset entries are filtered before they reach the remote API, so the contract with openboot.dev is preserved.Test plan
go vet ./...make test-unit(incl. archtest with refreshed baseline)TestMacOSPref_JSON_UnsetRoundTrip— marshal/unmarshal preserves Unset; omitempty when false.TestMacOSPref_JSON_LegacyDecodeNoUnsetField— old snapshots without the field still load.TestBuildImportConfig_MacOSPrefs_DropsUnset— boundary filter drops Unset entries before they hit state.TestCompareSnapshots_MacOS_IgnoresUnset— Unset on either side contributes nothing to diff.TestNewSnapshotEditor_UnsetPrefsDefaultUnselected— editor defaults Unset to unselected and surfaces the badge in the description.defaultsround-trip will exercise capture on macOS runners.