Skip to content

Commit 69a3a74

Browse files
fullstackjamclaude
andcommitted
refactor: overhaul diff, remove shell from snapshot, fix install flow
- diff: default to remote config (logged in) instead of useless local snapshot comparison. Error with guidance when not logged in. - diff: add dotfiles comparison (repo URL mismatch, uncommitted changes, unpushed commits) - diff: add macOS preferences comparison for remote configs - diff: fix formulae/casks mixing by filtering casks from remote packages - snapshot: remove ShellSnapshot from data structure — shell plugins and theme are part of dotfiles, not a separate concern - installer: stepShell now only installs oh-my-zsh; skips .zshrc modification when dotfiles are present (dotfiles manage .zshrc) - sync: remove shell diff and shell restore from sync plan - cleanup: remove SnapshotShellConfig, ShellDiff, GitDiff types and all related capture/compare/format/restore code (~600 lines removed) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 51c502b commit 69a3a74

23 files changed

+199
-798
lines changed

internal/cli/diff.go

Lines changed: 11 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -18,14 +18,15 @@ var diffCmd = &cobra.Command{
1818
1919
Sources (checked in order):
2020
1. --from <file> Compare against a snapshot file
21-
2. --user <username> Compare against your openboot.dev config
22-
3. Local snapshot Compare against ~/.openboot/snapshot.json
21+
2. --user <username> Compare against a specific openboot.dev config
22+
3. Logged in Compare against your own openboot.dev config
23+
4. Not logged in Error with guidance
2324
2425
This is a read-only operation — nothing is installed or removed.
2526
2627
Examples:
27-
openboot diff Diff against local snapshot
28-
openboot diff --user alice/my-config Diff against cloud config
28+
openboot diff Diff against your openboot.dev config (requires login)
29+
openboot diff --user alice/my-config Diff against someone else's config
2930
openboot diff --from my-setup.json Diff against a snapshot file
3031
openboot diff --json Output as JSON
3132
openboot diff --packages-only Only compare packages`,
@@ -61,7 +62,12 @@ func runDiff(cmd *cobra.Command) error {
6162
case user != "":
6263
result, err = diffFromRemote(system, user)
6364
default:
64-
result, err = diffFromLocalSnapshot(system)
65+
// If logged in, diff against the user's own remote config
66+
stored, authErr := auth.LoadToken()
67+
if authErr != nil || stored == nil {
68+
return fmt.Errorf("no comparison target specified\n\n Log in to diff against your openboot.dev config:\n openboot login\n\n Or specify a target explicitly:\n openboot diff --user <username> Compare against a remote config\n openboot diff --from <file> Compare against a snapshot file")
69+
}
70+
result, err = diffFromRemote(system, stored.Username)
6571
}
6672

6773
if err != nil {
@@ -111,12 +117,3 @@ func diffFromRemote(system *snapshot.Snapshot, userSlug string) (*diff.DiffResul
111117
return diff.CompareSnapshotToRemote(system, rc, source), nil
112118
}
113119

114-
func diffFromLocalSnapshot(system *snapshot.Snapshot) (*diff.DiffResult, error) {
115-
reference, err := snapshot.LoadLocal()
116-
if err != nil {
117-
return nil, fmt.Errorf("no local snapshot found — run 'openboot snapshot --local' first, or use --from or --user flags: %w", err)
118-
}
119-
120-
source := diff.Source{Kind: "local", Path: snapshot.LocalPath()}
121-
return diff.CompareSnapshots(system, reference, source), nil
122-
}

internal/cli/snapshot.go

Lines changed: 0 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -450,19 +450,6 @@ func showSnapshotSummary(snap *snapshot.Snapshot) {
450450
snapBoldStyle.Render("Preset:"))
451451
}
452452

453-
omzStatus := "not installed"
454-
if snap.Shell.OhMyZsh {
455-
theme := snap.Shell.Theme
456-
if theme == "" {
457-
theme = "default"
458-
}
459-
pluginCount := len(snap.Shell.Plugins)
460-
omzStatus = fmt.Sprintf("Oh-My-Zsh (%s theme, %d plugins)", theme, pluginCount)
461-
}
462-
fmt.Fprintf(os.Stderr, " %s %s + %s\n",
463-
snapBoldStyle.Render("Shell:"),
464-
snap.Shell.Default, omzStatus)
465-
466453
if snap.Git.UserName != "" || snap.Git.UserEmail != "" {
467454
fmt.Fprintf(os.Stderr, " %s %s <%s>\n",
468455
snapBoldStyle.Render("Git:"),
@@ -521,21 +508,6 @@ func showSnapshotPreview(snap *snapshot.Snapshot) {
521508
fmt.Fprintf(os.Stderr, " %s.%s = %s\n", pref.Domain, pref.Key, pref.Value)
522509
}
523510

524-
omzStatus := "not installed"
525-
if snap.Shell.OhMyZsh {
526-
omzStatus = "installed"
527-
}
528-
theme := snap.Shell.Theme
529-
if theme == "" {
530-
theme = "none"
531-
}
532-
plugins := "none"
533-
if len(snap.Shell.Plugins) > 0 {
534-
plugins = strings.Join(snap.Shell.Plugins, ", ")
535-
}
536-
fmt.Fprintf(os.Stderr, " %s %s (Oh-My-Zsh: %s, Theme: %s, Plugins: %s)\n",
537-
snapBoldStyle.Render("Shell:"), snap.Shell.Default, omzStatus, theme, plugins)
538-
539511
fmt.Fprintf(os.Stderr, " %s %s <%s>\n",
540512
snapBoldStyle.Render("Git:"), snap.Git.UserName, snap.Git.UserEmail)
541513

@@ -663,18 +635,6 @@ func showRestoreInfo(snap *snapshot.Snapshot, source string) {
663635
fmt.Fprintf(os.Stderr, " %s %s <%s>\n",
664636
snapBoldStyle.Render("Git:"), snap.Git.UserName, snap.Git.UserEmail)
665637
}
666-
if snap.Shell.OhMyZsh {
667-
theme := snap.Shell.Theme
668-
if theme == "" {
669-
theme = "default"
670-
}
671-
plugins := "none"
672-
if len(snap.Shell.Plugins) > 0 {
673-
plugins = strings.Join(snap.Shell.Plugins, ", ")
674-
}
675-
fmt.Fprintf(os.Stderr, " %s Oh-My-Zsh (theme: %s, plugins: %s)\n",
676-
snapBoldStyle.Render("Shell:"), theme, plugins)
677-
}
678638
fmt.Fprintln(os.Stderr)
679639
}
680640

@@ -749,12 +709,6 @@ func buildImportConfig(edited *snapshot.Snapshot, dryRun bool) *config.Config {
749709
UserEmail: edited.Git.UserEmail,
750710
}
751711

752-
cfg.SnapshotShell = &config.SnapshotShellConfig{
753-
OhMyZsh: edited.Shell.OhMyZsh,
754-
Theme: edited.Shell.Theme,
755-
Plugins: edited.Shell.Plugins,
756-
}
757-
758712
if edited.Dotfiles.RepoURL != "" {
759713
if err := config.ValidateDotfilesURL(edited.Dotfiles.RepoURL); err == nil {
760714
cfg.SnapshotDotfiles = edited.Dotfiles.RepoURL

internal/cli/snapshot_test.go

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -37,25 +37,18 @@ func TestBuildImportConfig_EmptyDotfiles(t *testing.T) {
3737
assert.Empty(t, cfg.DotfilesURL)
3838
}
3939

40-
func TestBuildImportConfig_GitAndShellPopulated(t *testing.T) {
40+
func TestBuildImportConfig_GitPopulated(t *testing.T) {
4141
snap := &snapshot.Snapshot{
4242
Git: snapshot.GitSnapshot{
4343
UserName: "Test User",
4444
UserEmail: "test@example.com",
4545
},
46-
Shell: snapshot.ShellSnapshot{
47-
OhMyZsh: true,
48-
Theme: "robbyrussell",
49-
Plugins: []string{"git"},
50-
},
5146
}
5247

5348
cfg := buildImportConfig(snap, false)
5449

5550
require.NotNil(t, cfg.SnapshotGit)
5651
assert.Equal(t, "Test User", cfg.SnapshotGit.UserName)
57-
require.NotNil(t, cfg.SnapshotShell)
58-
assert.True(t, cfg.SnapshotShell.OhMyZsh)
5952
}
6053

6154
func TestBuildImportConfig_EmptySnapshot(t *testing.T) {

internal/cli/sync.go

Lines changed: 0 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -200,21 +200,6 @@ func printSyncDiff(d *syncpkg.SyncDiff) {
200200
fmt.Println()
201201
}
202202

203-
// Shell changes
204-
if d.ShellChanged && d.ShellDiff != nil {
205-
fmt.Printf(" %s\n", ui.Green("Shell Changes"))
206-
if d.ShellDiff.ThemeChanged {
207-
fmt.Printf(" Theme: %s %s %s\n", d.ShellDiff.LocalTheme, ui.Yellow("→"), d.ShellDiff.RemoteTheme)
208-
}
209-
if len(d.ShellDiff.MissingPlugins) > 0 {
210-
fmt.Printf(" New plugins: %s\n", strings.Join(d.ShellDiff.MissingPlugins, ", "))
211-
}
212-
if len(d.ShellDiff.ExtraPlugins) > 0 {
213-
fmt.Printf(" Extra plugins: %s\n", strings.Join(d.ShellDiff.ExtraPlugins, ", "))
214-
}
215-
fmt.Println()
216-
}
217-
218203
// macOS changes
219204
if len(d.MacOSChanged) > 0 {
220205
fmt.Printf(" %s\n", ui.Green("macOS Changes"))
@@ -290,30 +275,6 @@ func buildSyncPlan(d *syncpkg.SyncDiff, rc *config.RemoteConfig, dryRun bool, in
290275
}
291276
}
292277

293-
// Shell changes
294-
if d.ShellChanged && d.ShellDiff != nil {
295-
if d.ShellDiff.ThemeChanged {
296-
apply, err := ui.Confirm(
297-
fmt.Sprintf("Update theme: %s → %s?", d.ShellDiff.LocalTheme, d.ShellDiff.RemoteTheme), true)
298-
if err != nil {
299-
return nil, fmt.Errorf("confirm theme: %w", err)
300-
}
301-
if apply {
302-
plan.UpdateTheme = d.ShellDiff.RemoteTheme
303-
}
304-
}
305-
if len(d.ShellDiff.MissingPlugins) > 0 {
306-
apply, err := ui.Confirm(
307-
fmt.Sprintf("Install plugins: %s?", strings.Join(d.ShellDiff.MissingPlugins, ", ")), true)
308-
if err != nil {
309-
return nil, fmt.Errorf("confirm plugins: %w", err)
310-
}
311-
if apply {
312-
plan.InstallPlugins = d.ShellDiff.MissingPlugins
313-
}
314-
}
315-
}
316-
317278
// macOS changes
318279
if len(d.MacOSChanged) > 0 {
319280
apply, err := ui.Confirm(fmt.Sprintf("Apply %d macOS preference change(s)?", len(d.MacOSChanged)), true)
@@ -357,13 +318,6 @@ func buildDryRunPlan(d *syncpkg.SyncDiff) *syncpkg.SyncPlan {
357318
InstallTaps: d.MissingTaps,
358319
}
359320

360-
if d.ShellDiff != nil {
361-
if d.ShellDiff.ThemeChanged {
362-
plan.UpdateTheme = d.ShellDiff.RemoteTheme
363-
}
364-
plan.InstallPlugins = d.ShellDiff.MissingPlugins
365-
}
366-
367321
if d.DotfilesChanged {
368322
plan.UpdateDotfiles = d.RemoteDotfiles
369323
}

internal/cli/sync_test.go

Lines changed: 1 addition & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -343,29 +343,6 @@ func TestPrintSyncDiffPackages(t *testing.T) {
343343
assert.Contains(t, output, "htop")
344344
}
345345

346-
func TestPrintSyncDiffShell(t *testing.T) {
347-
d := &syncpkg.SyncDiff{
348-
ShellChanged: true,
349-
ShellDiff: &syncpkg.ShellDiff{
350-
ThemeChanged: true,
351-
RemoteTheme: "agnoster",
352-
LocalTheme: "robbyrussell",
353-
MissingPlugins: []string{"zsh-autosuggestions"},
354-
ExtraPlugins: []string{"old-plugin"},
355-
},
356-
}
357-
358-
output := captureStdout(func() {
359-
printSyncDiff(d)
360-
})
361-
362-
assert.Contains(t, output, "Theme:")
363-
assert.Contains(t, output, "robbyrussell")
364-
assert.Contains(t, output, "agnoster")
365-
assert.Contains(t, output, "New plugins: zsh-autosuggestions")
366-
assert.Contains(t, output, "Extra plugins: old-plugin")
367-
}
368-
369346
func TestPrintSyncDiffMacOS(t *testing.T) {
370347
d := &syncpkg.SyncDiff{
371348
MacOSChanged: []syncpkg.MacOSPrefDiff{
@@ -469,13 +446,6 @@ func TestBuildDryRunPlan(t *testing.T) {
469446
MissingTaps: []string{"homebrew/cask-fonts"},
470447
DotfilesChanged: true,
471448
RemoteDotfiles: "https://github.com/user/dots",
472-
ShellChanged: true,
473-
ShellDiff: &syncpkg.ShellDiff{
474-
ThemeChanged: true,
475-
RemoteTheme: "agnoster",
476-
LocalTheme: "robbyrussell",
477-
MissingPlugins: []string{"zsh-autosuggestions"},
478-
},
479449
MacOSChanged: []syncpkg.MacOSPrefDiff{
480450
{Domain: "com.apple.dock", Key: "autohide", RemoteValue: "true", Desc: "Dock auto-hide"},
481451
},
@@ -487,8 +457,6 @@ func TestBuildDryRunPlan(t *testing.T) {
487457
assert.Equal(t, []string{"raycast"}, plan.InstallCasks)
488458
assert.Equal(t, []string{"turbo"}, plan.InstallNpm)
489459
assert.Equal(t, []string{"homebrew/cask-fonts"}, plan.InstallTaps)
490-
assert.Equal(t, "agnoster", plan.UpdateTheme)
491-
assert.Equal(t, []string{"zsh-autosuggestions"}, plan.InstallPlugins)
492460
assert.Equal(t, "https://github.com/user/dots", plan.UpdateDotfiles)
493461
assert.Len(t, plan.UpdateMacOSPrefs, 1)
494462
assert.Equal(t, "com.apple.dock", plan.UpdateMacOSPrefs[0].Domain)
@@ -500,12 +468,10 @@ func TestBuildDryRunPlanEmpty(t *testing.T) {
500468
assert.True(t, plan.IsEmpty())
501469
}
502470

503-
func TestBuildDryRunPlanNoShellDiff(t *testing.T) {
471+
func TestBuildDryRunPlanPackagesOnly(t *testing.T) {
504472
d := &syncpkg.SyncDiff{
505473
MissingFormulae: []string{"ripgrep"},
506474
}
507475
plan := buildDryRunPlan(d)
508476
assert.Equal(t, []string{"ripgrep"}, plan.InstallFormulae)
509-
assert.Empty(t, plan.UpdateTheme)
510-
assert.Empty(t, plan.InstallPlugins)
511477
}

internal/config/config.go

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,6 @@ type Config struct {
4848
RemoteConfig *RemoteConfig
4949
PackagesOnly bool
5050

51-
SnapshotShell *SnapshotShellConfig
5251
SnapshotGit *SnapshotGitConfig
5352
SnapshotMacOS []RemoteMacOSPref
5453
SnapshotDotfiles string
@@ -57,12 +56,6 @@ type Config struct {
5756
AllowPostInstall bool
5857
}
5958

60-
type SnapshotShellConfig struct {
61-
OhMyZsh bool
62-
Theme string
63-
Plugins []string
64-
}
65-
6659
type SnapshotGitConfig struct {
6760
UserName string
6861
UserEmail string

0 commit comments

Comments
 (0)