[object Object]

← back to Ticket System

Ticket desktop bar: top-align tiles in left/right docks + resizable/expandable bar

d9487c831303c7af2e2e8a11bebe4339b226c461 · 2026-09-03 11:23:49 -0700 · Steve Abrams

- Left/Right vertical docks now top-pin the tile group (alignTop) instead of centering
  it (alignCenterY stays for the Top bar) — no more dead space above the tiles.
- New 'Bar Bigger (expand)' / 'Bar Smaller' dropdown controls resize the WHOLE bar:
  width in the vertical docks (ticketBarVWidth, 180–700) and height in the Top bar
  (ticketBarHeight, 28–300); persisted per mode. Added a 'Change Dock' menu item too.
- 'Wider/Narrower' relabeled 'Tile Wider/Narrower' to distinguish tile vs bar sizing.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015XNcHvXXepEQar7Qsb7GGy

Files touched

Diff

commit d9487c831303c7af2e2e8a11bebe4339b226c461
Author: Steve Abrams <steve@designerwallcoverings.com>
Date:   Thu Sep 3 11:23:49 2026 -0700

    Ticket desktop bar: top-align tiles in left/right docks + resizable/expandable bar
    
    - Left/Right vertical docks now top-pin the tile group (alignTop) instead of centering
      it (alignCenterY stays for the Top bar) — no more dead space above the tiles.
    - New 'Bar Bigger (expand)' / 'Bar Smaller' dropdown controls resize the WHOLE bar:
      width in the vertical docks (ticketBarVWidth, 180–700) and height in the Top bar
      (ticketBarHeight, 28–300); persisted per mode. Added a 'Change Dock' menu item too.
    - 'Wider/Narrower' relabeled 'Tile Wider/Narrower' to distinguish tile vs bar sizing.
    
    Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
    Claude-Session: https://claude.ai/code/session_015XNcHvXXepEQar7Qsb7GGy
---
 desktop-bar/TicketBar.swift | 43 ++++++++++++++++++++++++++++++++++++-------
 1 file changed, 36 insertions(+), 7 deletions(-)

diff --git a/desktop-bar/TicketBar.swift b/desktop-bar/TicketBar.swift
index f523b331..553e7d39 100644
--- a/desktop-bar/TicketBar.swift
+++ b/desktop-bar/TicketBar.swift
@@ -85,6 +85,8 @@ private final class BarController: NSObject, NSApplicationDelegate, NSWindowDele
   private let vbarWidth: CGFloat = 300
   private var contentContainer: NSView!
   private var stackConstraints: [NSLayoutConstraint] = []
+  private var alignCenterY: NSLayoutConstraint!  // vertical centering (Top mode)
+  private var alignTop: NSLayoutConstraint!      // top-pinned (Left/Right docks)
   private var statusDetails: [String: [String]] = [:]
   private var workingDetails: [String] = []
   private var latestDetail = "No activity yet"
@@ -176,13 +178,14 @@ private final class BarController: NSObject, NSApplicationDelegate, NSWindowDele
     stack.translatesAutoresizingMaskIntoConstraints = false
     container.clipsToBounds = true
     container.addSubview(stack)
+    alignCenterY = stack.centerYAnchor.constraint(equalTo: container.centerYAnchor)
+    alignTop = stack.topAnchor.constraint(equalTo: container.topAnchor, constant: 10)
     stackConstraints = [
       stack.centerXAnchor.constraint(equalTo: container.centerXAnchor),
-      stack.centerYAnchor.constraint(equalTo: container.centerYAnchor),
       stack.leadingAnchor.constraint(greaterThanOrEqualTo: container.leadingAnchor, constant: 14),
       stack.trailingAnchor.constraint(lessThanOrEqualTo: container.trailingAnchor, constant: -10)
     ]
-    NSLayoutConstraint.activate(stackConstraints)
+    NSLayoutConstraint.activate(stackConstraints + [alignCenterY])
     hamburger = NSButton(title: "☰", target: self, action: #selector(openFromHamburger))
     hamburger.isBordered = false
     hamburger.font = .systemFont(ofSize: 18, weight: .bold)
@@ -214,6 +217,22 @@ private final class BarController: NSObject, NSApplicationDelegate, NSWindowDele
     applyPosition()
   }
 
+  // Persisted per-mode bar size: width for the vertical docks, height for the top bar.
+  private func vBarWidth() -> CGFloat { CGFloat((UserDefaults.standard.object(forKey: "ticketBarVWidth") as? Double) ?? 300) }
+  private func topBarHeight() -> CGFloat { CGFloat((UserDefaults.standard.object(forKey: "ticketBarHeight") as? Double) ?? 38) }
+  @objc private func barBigger(_ s: NSMenuItem)  { adjustBarSize(by: 1) }
+  @objc private func barSmaller(_ s: NSMenuItem) { adjustBarSize(by: -1) }
+  @objc private func cyclePositionMenu(_ s: NSMenuItem) { cyclePosition() }
+  // Expand/shrink the WHOLE bar: cross-axis width (left/right) or height (top).
+  private func adjustBarSize(by dir: CGFloat) {
+    if barPosition == "left" || barPosition == "right" {
+      UserDefaults.standard.set(Double(min(700, max(180, vBarWidth() + dir * 40))), forKey: "ticketBarVWidth")
+    } else if barPosition == "top" {
+      UserDefaults.standard.set(Double(min(300, max(28, topBarHeight() + dir * 16))), forKey: "ticketBarHeight")
+    }
+    applyPosition()
+  }
+
   // Dock the bar top (horizontal), left/right (vertical), or collapse to a ☰ hamburger.
   private func applyPosition() {
     guard let screen = panel.screen ?? NSScreen.main ?? NSScreen.screens.first else { return }
@@ -238,17 +257,21 @@ private final class BarController: NSObject, NSApplicationDelegate, NSWindowDele
     let vertical = (barPosition == "left" || barPosition == "right")
     stack.orientation = vertical ? .vertical : .horizontal
     stack.alignment = vertical ? .leading : .centerY
+    // Top-pin the tile group in the vertical docks; center it in the Top bar.
+    alignCenterY.isActive = !vertical
+    alignTop.isActive = vertical
+    let vW = self.vBarWidth()
     // Tile widths: uniform in a vertical bar; restore the horizontal widths on top.
     for tile in tiles {
-      tile.widthConstraint.constant = vertical ? (vbarWidth - 44) : (horizWidths[tile.key] ?? tile.widthConstraint.constant)
+      tile.widthConstraint.constant = vertical ? (vW - 44) : (horizWidths[tile.key] ?? tile.widthConstraint.constant)
     }
     switch barPosition {
     case "left":
-      panel.setFrame(NSRect(x: v.minX, y: v.minY, width: vbarWidth, height: v.height), display: true)
+      panel.setFrame(NSRect(x: v.minX, y: v.minY, width: vW, height: v.height), display: true)
     case "right":
-      panel.setFrame(NSRect(x: v.maxX - vbarWidth, y: v.minY, width: vbarWidth, height: v.height), display: true)
+      panel.setFrame(NSRect(x: v.maxX - vW, y: v.minY, width: vW, height: v.height), display: true)
     default: // top
-      let barHeight: CGFloat = 38
+      let barHeight = self.topBarHeight()
       let requested = CGFloat(UserDefaults.standard.double(forKey: "ticketBarYOffset"))
       let offset = min(max(0, requested), max(0, v.height - barHeight))
       panel.setFrame(NSRect(x: v.minX, y: v.maxY - barHeight - offset, width: v.width, height: barHeight), display: true)
@@ -390,7 +413,13 @@ private final class BarController: NSObject, NSApplicationDelegate, NSWindowDele
       menu.addItem(item)
     }
     menu.addItem(.separator())
-    for (title, action) in [("Move Left", #selector(moveLeft(_:))), ("Move Right", #selector(moveRight(_:))), ("Move Bar Up", #selector(moveBarUp(_:))), ("Move Bar Down", #selector(moveBarDown(_:))), ("Wider", #selector(wider(_:))), ("Narrower", #selector(narrower(_:)))] {
+    for (title, action) in [
+      ("Move Left", #selector(moveLeft(_:))), ("Move Right", #selector(moveRight(_:))),
+      ("Move Bar Up", #selector(moveBarUp(_:))), ("Move Bar Down", #selector(moveBarDown(_:))),
+      ("Tile Wider", #selector(wider(_:))), ("Tile Narrower", #selector(narrower(_:))),
+      ("Bar Bigger (expand)", #selector(barBigger(_:))), ("Bar Smaller", #selector(barSmaller(_:))),
+      ("Change Dock ▸ top/left/right/off", #selector(cyclePositionMenu(_:)))
+    ] {
       let item = NSMenuItem(title: title, action: action, keyEquivalent: "")
       item.target = self
       item.representedObject = key

← 452464b2 record TK-10928 durable monitoring evidence  ·  back to Ticket System  ·  record TK-11188 bounded monitoring proof 737fe045 →