Skip to content

Commit c3e0d7b

Browse files
committed
fix: apply remote config shell and macOS prefs during custom install
Three gaps fixed: 1. runCustomInstall now applies remote config shell settings via stepRestoreShell when the config includes a shell block, rather than always running the generic Oh-My-Zsh prompt. 2. runCustomInstall now applies remote config macos_prefs via stepRestoreMacOS when the config includes preferences, rather than always falling back to the 26 hardcoded defaults. 3. Snapshot editor macOS Prefs tab now allows manual add via '+'. Input format is domain.key=value (e.g. com.apple.dock.tilesize=48); type is auto-inferred at apply time. Added prefs are preserved through buildEditedSnapshot and included in the uploaded snapshot.
1 parent 248e34c commit c3e0d7b

3 files changed

Lines changed: 126 additions & 16 deletions

File tree

internal/installer/installer.go

Lines changed: 35 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -144,19 +144,48 @@ func runCustomInstall(cfg *config.Config) error {
144144
cfg.DotfilesURL = cfg.RemoteConfig.DotfilesRepo
145145
}
146146

147-
if err := stepShell(cfg); err != nil {
148-
ui.Error(fmt.Sprintf("Shell setup failed: %v", err))
149-
softErrs = append(softErrs, fmt.Errorf("shell: %w", err))
147+
if cfg.RemoteConfig.Shell != nil {
148+
cfg.SnapshotShell = &config.SnapshotShellConfig{
149+
OhMyZsh: cfg.RemoteConfig.Shell.OhMyZsh,
150+
Theme: cfg.RemoteConfig.Shell.Theme,
151+
Plugins: cfg.RemoteConfig.Shell.Plugins,
152+
}
153+
if err := stepRestoreShell(cfg); err != nil {
154+
ui.Error(fmt.Sprintf("Shell setup failed: %v", err))
155+
softErrs = append(softErrs, fmt.Errorf("shell: %w", err))
156+
}
157+
} else {
158+
if err := stepShell(cfg); err != nil {
159+
ui.Error(fmt.Sprintf("Shell setup failed: %v", err))
160+
softErrs = append(softErrs, fmt.Errorf("shell: %w", err))
161+
}
150162
}
151163

152164
if err := stepDotfiles(cfg); err != nil {
153165
ui.Error(fmt.Sprintf("Dotfiles setup failed: %v", err))
154166
softErrs = append(softErrs, fmt.Errorf("dotfiles: %w", err))
155167
}
156168

157-
if err := stepMacOS(cfg); err != nil {
158-
ui.Error(fmt.Sprintf("macOS configuration failed: %v", err))
159-
softErrs = append(softErrs, fmt.Errorf("macos: %w", err))
169+
if len(cfg.RemoteConfig.MacOSPrefs) > 0 {
170+
cfg.SnapshotMacOS = make([]config.SnapshotMacOSPref, len(cfg.RemoteConfig.MacOSPrefs))
171+
for i, p := range cfg.RemoteConfig.MacOSPrefs {
172+
cfg.SnapshotMacOS[i] = config.SnapshotMacOSPref{
173+
Domain: p.Domain,
174+
Key: p.Key,
175+
Type: p.Type,
176+
Value: p.Value,
177+
Desc: p.Desc,
178+
}
179+
}
180+
if err := stepRestoreMacOS(cfg); err != nil {
181+
ui.Error(fmt.Sprintf("macOS configuration failed: %v", err))
182+
softErrs = append(softErrs, fmt.Errorf("macos: %w", err))
183+
}
184+
} else {
185+
if err := stepMacOS(cfg); err != nil {
186+
ui.Error(fmt.Sprintf("macOS configuration failed: %v", err))
187+
softErrs = append(softErrs, fmt.Errorf("macos: %w", err))
188+
}
160189
}
161190

162191
if err := stepPostInstall(cfg); err != nil {

internal/ui/snapshot_editor.go

Lines changed: 52 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ const (
2424
type editorItem struct {
2525
name string
2626
description string
27+
value string // for macOS pref items: the raw preference value
2728
selected bool
2829
itemType editorItemType
2930
isAdded bool // true = user added this, not from original snapshot
@@ -192,11 +193,6 @@ func (m SnapshotEditorModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
192193
m = m.withFilteredItems()
193194

194195
case msg.String() == "+":
195-
// Block manual add on macOS Prefs tab (prefs require domain.key structure)
196-
if m.tabs[m.activeTab].itemType == editorItemMacOSPref {
197-
m.toastMessage = "Cannot manually add macOS prefs"
198-
return m, editorToastClearCmd()
199-
}
200196
m.addMode = true
201197
m.addInput = ""
202198

@@ -321,6 +317,43 @@ func (m SnapshotEditorModel) updateAddMode(msg tea.KeyMsg) (tea.Model, tea.Cmd)
321317
return m, nil
322318
case "enter":
323319
if m.addInput != "" {
320+
if m.tabs[m.activeTab].itemType == editorItemMacOSPref {
321+
// Expect "domain.key=value" format
322+
eqIdx := strings.Index(m.addInput, "=")
323+
if eqIdx > 0 {
324+
domainKey := m.addInput[:eqIdx]
325+
value := m.addInput[eqIdx+1:]
326+
if strings.Contains(domainKey, ".") && value != "" {
327+
tab := &m.tabs[m.activeTab]
328+
duplicate := false
329+
for _, item := range tab.items {
330+
if item.name == domainKey {
331+
duplicate = true
332+
break
333+
}
334+
}
335+
if !duplicate {
336+
tab.items = append(tab.items, editorItem{
337+
name: domainKey,
338+
description: fmt.Sprintf("= %s", value),
339+
value: value,
340+
selected: true,
341+
itemType: editorItemMacOSPref,
342+
isAdded: true,
343+
})
344+
m.toastMessage = fmt.Sprintf("+ Added %s", domainKey)
345+
m.addMode = false
346+
m.addInput = ""
347+
m.cursor = len(tab.items) - 1
348+
return m, editorToastClearCmd()
349+
}
350+
}
351+
}
352+
m.toastMessage = "Format: domain.key=value (e.g. com.apple.dock.tilesize=48)"
353+
m.addMode = false
354+
m.addInput = ""
355+
return m, editorToastClearCmd()
356+
}
324357
tab := &m.tabs[m.activeTab]
325358
// Check for duplicates
326359
duplicate := false
@@ -410,7 +443,11 @@ func (m SnapshotEditorModel) View() string {
410443
lines = append(lines, "")
411444
tabName := m.tabs[m.activeTab].name
412445
lines = append(lines, activeTabStyle.Render(fmt.Sprintf("Add to %s: %s▌", tabName, m.addInput)))
413-
lines = append(lines, descStyle.Render(" Type a package name and press Enter to add, Esc to cancel"))
446+
if m.tabs[m.activeTab].itemType == editorItemMacOSPref {
447+
lines = append(lines, descStyle.Render(" Format: domain.key=value (e.g. com.apple.dock.tilesize=48) · Enter to add, Esc to cancel"))
448+
} else {
449+
lines = append(lines, descStyle.Render(" Type a package name and press Enter to add, Esc to cancel"))
450+
}
414451
lines = append(lines, "")
415452
}
416453

@@ -753,6 +790,15 @@ func buildEditedSnapshot(original *snapshot.Snapshot, m *SnapshotEditorModel) *s
753790
case editorItemMacOSPref:
754791
if pref, ok := originalPrefs[item.name]; ok {
755792
edited.MacOSPrefs = append(edited.MacOSPrefs, pref)
793+
} else if item.isAdded {
794+
dotIdx := strings.Index(item.name, ".")
795+
if dotIdx > 0 {
796+
edited.MacOSPrefs = append(edited.MacOSPrefs, snapshot.MacOSPref{
797+
Domain: item.name[:dotIdx],
798+
Key: item.name[dotIdx+1:],
799+
Value: item.value,
800+
})
801+
}
756802
}
757803
}
758804
}

internal/ui/snapshot_editor_test.go

Lines changed: 39 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -702,15 +702,50 @@ func TestSnapshotEditorAddToNpmTab(t *testing.T) {
702702
assert.True(t, found, "prettier should be in NPM tab")
703703
}
704704

705-
func TestSnapshotEditorAddModeBlockedOnMacOSPrefs(t *testing.T) {
705+
func TestSnapshotEditorAddMacOSPrefActivatesAddMode(t *testing.T) {
706706
m := NewSnapshotEditor(makeTestSnapshot())
707707
m.activeTab = 4 // macOS Prefs tab
708708

709-
result, cmd := m.Update(tea.KeyMsg{Type: tea.KeyRunes, Runes: []rune("+")})
709+
result, _ := m.Update(tea.KeyMsg{Type: tea.KeyRunes, Runes: []rune("+")})
710710
updated := result.(SnapshotEditorModel)
711711

712-
assert.False(t, updated.addMode, "add mode should not activate on macOS Prefs tab")
713-
assert.Contains(t, updated.toastMessage, "Cannot manually add macOS prefs")
712+
assert.True(t, updated.addMode, "add mode should activate on macOS Prefs tab")
713+
}
714+
715+
func TestSnapshotEditorAddMacOSPrefValidFormat(t *testing.T) {
716+
m := NewSnapshotEditor(makeTestSnapshot())
717+
m.activeTab = 4 // macOS Prefs tab
718+
m.addMode = true
719+
m.addInput = "com.apple.dock.tilesize=64"
720+
721+
result, cmd := m.Update(tea.KeyMsg{Type: tea.KeyEnter})
722+
updated := result.(SnapshotEditorModel)
723+
724+
assert.False(t, updated.addMode)
725+
assert.NotNil(t, cmd)
726+
found := false
727+
for _, item := range updated.tabs[4].items {
728+
if item.name == "com.apple.dock.tilesize" {
729+
found = true
730+
assert.Equal(t, "64", item.value)
731+
assert.True(t, item.isAdded)
732+
assert.Equal(t, editorItemMacOSPref, item.itemType)
733+
}
734+
}
735+
assert.True(t, found, "pref should be added to macOS Prefs tab")
736+
}
737+
738+
func TestSnapshotEditorAddMacOSPrefInvalidFormat(t *testing.T) {
739+
m := NewSnapshotEditor(makeTestSnapshot())
740+
m.activeTab = 4 // macOS Prefs tab
741+
m.addMode = true
742+
m.addInput = "invalid-no-equals"
743+
744+
result, cmd := m.Update(tea.KeyMsg{Type: tea.KeyEnter})
745+
updated := result.(SnapshotEditorModel)
746+
747+
assert.False(t, updated.addMode)
748+
assert.Contains(t, updated.toastMessage, "Format:")
714749
assert.NotNil(t, cmd)
715750
}
716751

0 commit comments

Comments
 (0)