Skip to content

feat: native dock pinning and login items (CLI side)#129

Merged
fullstackjam merged 15 commits into
mainfrom
feat/dock-and-login-items
Jun 8, 2026
Merged

feat: native dock pinning and login items (CLI side)#129
fullstackjam merged 15 commits into
mainfrom
feat/dock-and-login-items

Conversation

@fullstackjam

Copy link
Copy Markdown
Collaborator

What does this PR do?

Promotes Dock pinning and Login Items from post_install shell blobs to first-class snapshot capture + declarative install fields on the CLI side.

Why?

Today users pin Dock apps and register Login Items by hand-writing defaults write com.apple.dock persistent-apps -array-add '<plist xml>' and osascript tell application "System Events" blocks inside the `post_install` array — both are common-enough macOS setup actions that they deserve first-class treatment alongside Homebrew packages and defaults preferences.

After this PR:

  • openboot snapshot automatically captures dock_apps (list of app paths) and login_items (list of {name, path, hidden}) from the live system.
  • openboot install from a RemoteConfig applies them declaratively (wipe-and-rebuild — install completes with the Dock / Login Items list matching the config exactly).
  • post_install is unchanged; users may keep existing scripts or migrate to the new fields.

Spec: `docs/superpowers/specs/2026-06-07-dock-and-login-items-design.md` (local, gitignored).
Plan: `docs/superpowers/plans/2026-06-07-dock-and-login-items-cli.md` (local, gitignored).

Testing

  • go vet ./... passes
  • L1 (`make test-unit`) green, including archtest baselines
  • Unit tests added: 5 Dock parser + 5 login-items parser + 1 Dock -blob regression guard + dry-run command-sequence assertions for SetDockApps / SetLoginItems + installer subtask integration tests
  • Manual smoke on real machine: openboot snapshot --json | jq '{dock_apps, login_items}' returns the live Dock and Login Items correctly

Cross-repo checklist

  • Does this need a docs/content update in openboot.dev?Yes, follow-up PR. The server-side schema (RemoteConfig TS type + dashboard list editors + inline install-script generator) is intentionally scoped out — this PR only ships the CLI side so users can snapshot/install from local JSON files immediately. Plan 2 covers the web side.
  • Does this change the CLI ↔ server API contract? — Additively yes (two new optional fields on RemoteConfig). openboot-contract schema update should land alongside the web work. Old CLIs ignore unknown fields; new CLI treats missing fields as "skip subtask", so wire compatibility is preserved.

Notes for reviewer

Known limitations (intentional, out of scope):

  1. `PlanFromSnapshot` (the openboot install --import snap.json path) doesn't carry dock/login items yet — needs SnapshotDockApps/SnapshotLoginItems on config.InstallState. RemoteConfig install path works fully.
  2. Three `LoginItem` types coexist (snapshot/config/macos) due to unavoidable import-cycle constraints; mapping happens at `installer/plan.go`. Could be unified later by moving to an internal/types package.
  3. macos/dock.go and macos/loginitems.go use raw fmt.Print* for dry-run output (baselined in fmtprint.txt) because the macos package can't import ui (cycle via snapshot). Follows existing precedent — macos.go already had 3 such baselined lines.

Real bug found during verification: initial implementation used `plutil -extract persistent-apps json` which fails on real Dock plists because `tile-data` contains `` blobs (alias bookmarks, icon thumbnails). Switched to `xml1` + stdlib encoding/xml token walker. Regression test `TestParseDockAppsXML_DataBlobRegression` guards against silent reintroduction.

Archtest baseline changes:

  • `fmtprint.txt`: +8 lines for new files in `internal/macos/` (import-cycle workaround)
  • `dryrun.txt` exempt list: +2 entries for `internal/snapshot/dock.go` and `internal/snapshot/loginitems.go` (read-only system probes, same rationale as already-exempted `capture.go`)
  • `no-direct-exec.txt`: line-number drift only (pre-existing `exec.Command` in `applyPostInstall` shifted because `applyMacOSPrefs` was restructured)

🤖 Generated with Claude Code

fullstackjam and others added 15 commits June 7, 2026 23:33
Introduces the LoginItem struct and adds DockApps []string and
LoginItems []LoginItem (both omitempty) to the Snapshot struct, with
round-trip JSON tests confirming serialisation and omitempty behaviour.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Wires CaptureDockApps (sh -c pipeline: defaults export | plutil -extract)
into CaptureWithProgress as a new step after "macOS Preferences", populates
CaptureResults.DockApps, and copies it through assembleSnapshot to Snapshot.DockApps.
Adds a no-panic integration guard test for the live capture path.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Comment said the function "may return an error" — actually swallows them.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Wire CaptureLoginItems (osascript via system.RunCommandOutput) into
the capture pipeline immediately after the Dock Apps step; register
LoginItems in CaptureResults with nil-guard and assembleSnapshot copy.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Both are read-only system probes (defaults export, osascript reads) used
during snapshot capture, mirroring the existing capture.go exemption.
Add LoginItem (parallel struct, no snapshot import to avoid import cycle)
and DockApps/LoginItems fields to RemoteConfig so the CLI can decode
configs from the openboot.dev API that include these new fields.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds macos.SetDockApps(apps []string, dryRun bool) error which clears
persistent-apps, re-adds each present app as a plist tile, and restarts
the Dock. Missing apps are skipped with a stderr warning. Raw fmt.Print*
calls are used instead of ui.* helpers because the macos package cannot
import ui (import cycle via snapshot); new lines are baselined in
internal/archtest/baseline/fmtprint.txt with a justification comment.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Implements SetLoginItems(items []LoginItem, dryRun bool) error in the
macos package. Defines a local LoginItem type to avoid the
snapshot→macos import cycle. Dry-run output uses raw fmt.Print* (same
pattern as SetDockApps); new violations baselined in fmtprint.txt.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Add DockApps []string and LoginItems []macos.LoginItem to InstallPlan and
map them from RemoteConfig in planFromRemoteConfig, mirroring the existing
MacOSPrefs pattern. PlanFromSnapshot is left unchanged — InstallState has
no SnapshotDockApps/SnapshotLoginItems fields yet (Task 1 only added them
to snapshot.Snapshot and config.RemoteConfig, not InstallState).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Wire macos.SetDockApps and macos.SetLoginItems into applyMacOSPrefs,
restructuring it into three subtasks (defaults, dock, login items) with
errors.Join aggregation; nil fields still short-circuit their subtask.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ucture

Task 10's restructure of applyMacOSPrefs shifted the pre-existing
exec.Command in applyPostInstall from line 85 to line 132. Same call,
no new violations.
defaults export | plutil -extract persistent-apps json fails on real
Dock plists because tile-data contains <data> blobs (alias bookmarks,
icon thumbnails). Switch to xml1 + stdlib encoding/xml token walker so
capture works on actual machines, not just synthetic fixtures.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@github-actions github-actions Bot added installer Package installation logic tests Tests only snapshot Snapshot capture/restore labels Jun 8, 2026
@fullstackjam fullstackjam merged commit 072ff70 into main Jun 8, 2026
12 checks passed
@fullstackjam fullstackjam deleted the feat/dock-and-login-items branch June 8, 2026 13:54
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

installer Package installation logic snapshot Snapshot capture/restore tests Tests only

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant