Show profile paths, report the build version, and colour each profile

- 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.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-04 22:44:21 +06:00
co-authored by Claude Opus 5
parent fd13f2704e
commit 68e74fcbbb
6 changed files with 90 additions and 10 deletions
+51 -7
View File
@@ -58,6 +58,9 @@ private final class ProfilesViewController: NSViewController, NSTableViewDataSou
private var infos: [ProfileInfo] = []
private let tableView = NSTableView()
private let removeButton = NSButton(title: "\u{2212}", target: nil, action: nil)
private let detailStack = NSStackView()
private let detailAppValue = NSTextField(labelWithString: "")
private let detailCodeValue = NSTextField(labelWithString: "")
private let addForm = NSStackView()
private let nameField = NSTextField()
private let codeField = NSTextField()
@@ -91,11 +94,15 @@ private final class ProfilesViewController: NSViewController, NSTableViewDataSou
buttonRow.spacing = 4
buttonRow.translatesAutoresizingMaskIntoConstraints = false
buildDetailStack()
detailStack.isHidden = true
detailStack.translatesAutoresizingMaskIntoConstraints = false
buildAddForm()
addForm.isHidden = true
addForm.translatesAutoresizingMaskIntoConstraints = false
let stack = NSStackView(views: [scroll, buttonRow, addForm])
let stack = NSStackView(views: [scroll, detailStack, buttonRow, addForm])
stack.orientation = .vertical
stack.spacing = 8
stack.edgeInsets = NSEdgeInsets(top: 12, left: 12, bottom: 12, right: 12)
@@ -108,9 +115,36 @@ private final class ProfilesViewController: NSViewController, NSTableViewDataSou
stack.trailingAnchor.constraint(equalTo: view.trailingAnchor),
stack.bottomAnchor.constraint(equalTo: view.bottomAnchor),
scroll.heightAnchor.constraint(greaterThanOrEqualToConstant: 160),
detailStack.widthAnchor.constraint(equalTo: stack.widthAnchor),
])
}
// Shows the selected profile's two dirs — the thing you'd otherwise
// have to right-click "Reveal" to find out.
private func buildDetailStack() {
for value in [detailAppValue, detailCodeValue] {
value.isSelectable = true
value.textColor = .secondaryLabelColor
value.font = .systemFont(ofSize: NSFont.smallSystemFontSize)
value.lineBreakMode = .byTruncatingMiddle
value.setContentCompressionResistancePriority(.defaultLow, for: .horizontal)
}
let appRow = NSStackView(views: [labeled("Claude Desktop:"), detailAppValue])
appRow.orientation = .horizontal
appRow.spacing = 6
let codeRow = NSStackView(views: [labeled("Claude Code:"), detailCodeValue])
codeRow.orientation = .horizontal
codeRow.spacing = 6
detailStack.orientation = .vertical
detailStack.alignment = .leading
detailStack.spacing = 2
[appRow, codeRow].forEach(detailStack.addArrangedSubview)
appRow.widthAnchor.constraint(equalTo: detailStack.widthAnchor).isActive = true
codeRow.widthAnchor.constraint(equalTo: detailStack.widthAnchor).isActive = true
}
private func buildAddForm() {
nameField.placeholderString = "Profile name"
nameField.delegate = self
@@ -156,9 +190,21 @@ private final class ProfilesViewController: NSViewController, NSTableViewDataSou
func update(_ infos: [ProfileInfo]) {
self.infos = infos
tableView.reloadData()
removeButton.isEnabled = tableView.selectedRow >= 0
&& infos.indices.contains(tableView.selectedRow)
&& infos[tableView.selectedRow].profile.name != "default"
refreshForSelection()
}
private func refreshForSelection() {
let row = tableView.selectedRow
guard infos.indices.contains(row) else {
removeButton.isEnabled = false
detailStack.isHidden = true
return
}
let profile = infos[row].profile
removeButton.isEnabled = profile.name != "default"
detailAppValue.stringValue = profile.appDir
detailCodeValue.stringValue = profile.codeDir
detailStack.isHidden = false
}
// MARK: NSTableViewDataSource / Delegate
@@ -198,9 +244,7 @@ private final class ProfilesViewController: NSViewController, NSTableViewDataSou
}
func tableViewSelectionDidChange(_ notification: Notification) {
removeButton.isEnabled = tableView.selectedRow >= 0
&& infos.indices.contains(tableView.selectedRow)
&& infos[tableView.selectedRow].profile.name != "default"
refreshForSelection()
}
// MARK: NSTextFieldDelegate — live default-path preview as the name is typed