The previous layout was a fixed 460x360 window regardless of tab or
state, with a plain unbordered table and text +/- buttons — it read as a
generic cross-platform port rather than something native. Several rows
also had no real width anchor at all, so they floated: flush against the
window's left edge with no margin, and in the add form's case wide enough
to overflow past the right edge.
- The window is no longer manually resizable. Each tab reports its own
preferredContentSize, recomputed in viewDidLayout and after every state
change, and NSTabViewController resizes the window to match. That is
what actually fixes the large empty areas: a tab with less content gets
a smaller window rather than sitting inside a fixed larger one.
- Every row now anchors its width to the one element with a real absolute
width, fixing both the edge-touching and the overflow.
- Table gains a bezel border and alternating rows; form labels get a
fixed trailing-aligned width so fields line up; the error label wraps
instead of truncating; tab padding is consistent.
- About moves out of a separate panel into a third tab, with the app icon
centred and enlarged to 96px.
Verified visually with a standalone harness that drives the real window
and screenshots it, rather than simulating input — scripting clicks into
the app itself needs an Accessibility grant this environment lacks.
- Selecting a row in the Profiles tab shows its Claude Desktop and Claude
Code dirs inline and selectable, instead of requiring a right-click
"Reveal in Finder" to find out where a profile actually lives.
- build.sh stamps Contents/Resources/COMMIT with the short HEAD SHA, left
empty when HEAD sits exactly on a tag (how a real release is built),
since the version number alone is unambiguous there. About appends it
when present, so two dev builds off the same VERSION are
distinguishable. The update checker still compares plain semver.
- Profile dot colours are picked per NSApp.effectiveAppearance at render
time: same hue either way, but full saturation with brightness split
per background (1.0 dark, 0.55 light), so they read as bold hues rather
than washing out on white or muddying against a dark menu.
Replaces the original shannoncoat.sh entirely with in-process Swift. The
script stopped earning its keep once the rest went native, and being a
persistent process rather than a one-shot CLI is what later makes live
window tracking possible at all.
- ProfileStore: JSON config at ~/.shannoncoat/<name>.json (was hand-rolled
YAML), with ~ expansion, paths relative to the config dir, and
name/dir-collision validation in one place.
- ProcessInspector: sysctl(KERN_PROC_ALL/KERN_PROCARGS2) enumeration
instead of shelling out to pgrep.
- ClaudeControl: launch via Process, focus via direct Accessibility calls
(unhide, un-minimize, poll-for-window, AXRaise) instead of AppleScript,
and a quit that respects Claude's own termination handling rather than
unconditionally SIGKILLing after a flat 5s timeout.
- LiveState: NSWorkspace notification-driven state with no polling timer,
so the menu bar reflects reality within about a second — including
changes made outside the app entirely.
- ManageWindow: one non-modal window replacing what would otherwise be a
string of separate popup alerts, with inline add/remove and directory
pickers.
- LaunchAtLogin / UpdateChecker: SMAppService login-item toggle, and a
minimal VERSION-file update check that alerts on a manual check and
posts a quiet notification on the automatic one.
Also drops the standalone CLI in favour of GUI-only, and adds a
placeholder app icon drawn from vector shapes rather than composited on
top of Claude's own icon assets.
Claude Desktop is located at runtime rather than assumed: a path the user
picked previously, then /Applications, then a per-user ~/Applications
install, each accepted only if it actually contains the executable. If
none match, launching a profile asks the user to locate it once and
remembers the answer — a launch that silently does nothing gives them no
way to work out what's wrong.