Monorepo for Aesthetic.Computer aesthetic.computer
4
fork

Configure Feed

Select the types of activity you want to include in your feed.

slab/menuband: tighter popover — chevron-arrow octave, "MIDI" row, smaller insets

Layout pass: less negative space, fewer captions.

Octave control:
- Big monospaced "+0" label (18 pt, semibold) on the left of the title
row, then a tight pair of chevron-arrow buttons on its right.
- NSStepper still drives the value (so controller API doesn't change),
but it's now hidden — the visible UI is just the number + arrows.
octaveDown / octaveUp actions nudge the stepper's integerValue and
call back to controller.octaveShift.
- Reset button removed; the arrows step to 0 just as fast.

MIDI switch:
- Row label simplified from "Send MIDI to DAW" / sublabel "Routes via
virtual port" to a single bold "MIDI" left-aligned, switch on the
right. One line. Half the vertical space.

Popover overall:
- Stack spacing 10 → 6, edge insets 14×16 → 10×12. Trims ~30 px of
vertical filler across the whole popover.

Site:
- Bump download cache-buster query string to ?v=eb9687b.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

+70 -22
+69 -21
slab/menuband/Sources/MenuBand/MenuBandPopover.swift
··· 81 81 root.layer?.backgroundColor = NSColor.windowBackgroundColor.cgColor 82 82 root.translatesAutoresizingMaskIntoConstraints = false 83 83 84 - // Vertical stack of rows. 84 + // Vertical stack of rows. Tight spacing + smaller edge insets so 85 + // the popover doesn't carry a lot of negative space. 85 86 let stack = NSStackView() 86 87 stack.orientation = .vertical 87 88 stack.alignment = .leading 88 - stack.spacing = 10 89 - stack.edgeInsets = NSEdgeInsets(top: 14, left: 16, bottom: 14, right: 16) 89 + stack.spacing = 6 90 + stack.edgeInsets = NSEdgeInsets(top: 10, left: 12, bottom: 10, right: 12) 90 91 stack.translatesAutoresizingMaskIntoConstraints = false 91 92 root.addSubview(stack) 92 93 ··· 117 118 titleSpacer.setContentHuggingPriority(.defaultLow, for: .horizontal) 118 119 titleRow.addArrangedSubview(titleSpacer) 119 120 120 - // Compact octave: stepper + monospaced label, no "Octave" caption 121 - // (small enough that the stepper itself reads). Reset is dropped 122 - // — the stepper can walk back to 0 just as fast. 121 + // Compact octave: large monospaced number, then a tight pair of 122 + // chevron arrows to its right. NSStepper kept invisibly as the 123 + // value model so the rest of the controller's API doesn't change; 124 + // the visible buttons just nudge its `integerValue`. 123 125 octaveLabel = NSTextField(labelWithString: "+0") 124 - octaveLabel.font = NSFont.monospacedSystemFont(ofSize: 11, weight: .bold) 125 - octaveLabel.textColor = .secondaryLabelColor 126 + octaveLabel.font = NSFont.monospacedDigitSystemFont(ofSize: 18, weight: .bold) 127 + octaveLabel.textColor = .labelColor 126 128 octaveLabel.alignment = .right 127 - octaveLabel.widthAnchor.constraint(equalToConstant: 24).isActive = true 129 + octaveLabel.widthAnchor.constraint(equalToConstant: 30).isActive = true 130 + 128 131 octaveStepper = NSStepper() 129 132 octaveStepper.minValue = -4 130 133 octaveStepper.maxValue = 4 ··· 132 135 octaveStepper.valueWraps = false 133 136 octaveStepper.target = self 134 137 octaveStepper.action = #selector(octaveChanged(_:)) 135 - let octaveCaption = NSTextField(labelWithString: "octave") 136 - octaveCaption.font = NSFont.systemFont(ofSize: 10) 137 - octaveCaption.textColor = .tertiaryLabelColor 138 - titleRow.addArrangedSubview(octaveCaption) 138 + octaveStepper.isHidden = true // value model only — UI is the arrows below 139 + 140 + let leftArrow = NSButton() 141 + leftArrow.image = NSImage(systemSymbolName: "chevron.left", 142 + accessibilityDescription: "Octave down") 143 + leftArrow.bezelStyle = .recessed 144 + leftArrow.controlSize = .small 145 + leftArrow.imagePosition = .imageOnly 146 + leftArrow.target = self 147 + leftArrow.action = #selector(octaveDown) 148 + 149 + let rightArrow = NSButton() 150 + rightArrow.image = NSImage(systemSymbolName: "chevron.right", 151 + accessibilityDescription: "Octave up") 152 + rightArrow.bezelStyle = .recessed 153 + rightArrow.controlSize = .small 154 + rightArrow.imagePosition = .imageOnly 155 + rightArrow.target = self 156 + rightArrow.action = #selector(octaveUp) 157 + 158 + let octaveArrows = NSStackView(views: [leftArrow, rightArrow]) 159 + octaveArrows.orientation = .horizontal 160 + octaveArrows.spacing = 1 161 + 139 162 titleRow.addArrangedSubview(octaveLabel) 140 - titleRow.addArrangedSubview(octaveStepper) 163 + titleRow.addArrangedSubview(octaveArrows) 164 + titleRow.addArrangedSubview(octaveStepper) // hidden, here for layout-time only 141 165 stack.addArrangedSubview(titleRow) 142 166 titleRow.widthAnchor.constraint(equalTo: stack.widthAnchor, 143 - constant: -32).isActive = true 167 + constant: -24).isActive = true 144 168 145 169 // Update banner — hidden until UpdateChecker reports a newer 146 170 // release. Tinted accent so the user notices it without it feeling ··· 220 244 221 245 stack.addArrangedSubview(makeSeparator()) 222 246 223 - // MIDI switch row. 247 + // MIDI switch — single-line, just "MIDI" + the switch. 224 248 midiSwitch = NSSwitch() 225 249 midiSwitch.target = self 226 250 midiSwitch.action = #selector(midiSwitchToggled(_:)) 227 - stack.addArrangedSubview(makeSwitchRow( 228 - label: "Send MIDI to DAW", 229 - sublabel: "Routes via virtual port", 230 - switchControl: midiSwitch 231 - )) 251 + let midiRow = NSStackView() 252 + midiRow.orientation = .horizontal 253 + midiRow.alignment = .centerY 254 + midiRow.spacing = 8 255 + midiRow.distribution = .fill 256 + let midiLabel = NSTextField(labelWithString: "MIDI") 257 + midiLabel.font = NSFont.systemFont(ofSize: 12, weight: .semibold) 258 + midiLabel.textColor = .labelColor 259 + let midiSpacer = NSView() 260 + midiSpacer.setContentHuggingPriority(.defaultLow, for: .horizontal) 261 + midiRow.addArrangedSubview(midiLabel) 262 + midiRow.addArrangedSubview(midiSpacer) 263 + midiRow.addArrangedSubview(midiSwitch) 264 + stack.addArrangedSubview(midiRow) 265 + midiRow.widthAnchor.constraint(equalTo: stack.widthAnchor, constant: -24).isActive = true 232 266 233 267 // MIDI self-test status — populated by the controller after each 234 268 // toggle-on. Empty when MIDI is off. ··· 607 641 menuBand?.octaveShift = 0 608 642 octaveStepper.integerValue = 0 609 643 updateOctaveLabel(0) 644 + } 645 + 646 + @objc private func octaveDown() { 647 + let new = max(Int(octaveStepper.minValue), octaveStepper.integerValue - 1) 648 + octaveStepper.integerValue = new 649 + menuBand?.octaveShift = new 650 + updateOctaveLabel(new) 651 + } 652 + 653 + @objc private func octaveUp() { 654 + let new = min(Int(octaveStepper.maxValue), octaveStepper.integerValue + 1) 655 + octaveStepper.integerValue = new 656 + menuBand?.octaveShift = new 657 + updateOctaveLabel(new) 610 658 } 611 659 612 660 @objc private func openAesthetic() {
+1 -1
system/public/menuband/index.html
··· 879 879 <p class="tagline">Built-in macOS instruments, in the menu bar.</p> 880 880 881 881 <div class="button-row"> 882 - <a class="aqua" href="https://assets.aesthetic.computer/menuband/Menu-Band-0.1.dmg?v=4b845f9" download> 882 + <a class="aqua" href="https://assets.aesthetic.computer/menuband/Menu-Band-0.1.dmg?v=eb9687b" download> 883 883 Download 884 884 <small>0.1 · Apple Silicon · 1.1 MB</small> 885 885 </a>