The combo drops its padding and turns portrait — a 4:5 field puts the symbol dead center

Owner's first review of the combo rework (2026-08-09): remove the face padding, make the
field taller and narrower at about a 4:5 width:height ratio, and center the symbol glyph in
its face. All three land in ComboFieldMetrics, so ColorComboControl and SymbolComboControl
stay the identical shape they were built to share.

- ComboFieldMetrics grows a width figure (height * widthToHeightRatio, 0.8), replacing
  NSView.noIntrinsicMetric — every combo now carries its own taller, narrower intrinsic size
  instead of stretching to whatever a caller's frame proposed.
- facePaddingH/facePaddingV/glyphPadding are gone; a face fills its zone edge to edge.
  glyphPointSize is now whichever of the face's own width/height is smaller, with nothing
  subtracted for padding that no longer exists.
- SymbolComboControl.drawFace centers the glyph on both axes — it only ever centered
  vertically before, despite its own doc comment claiming otherwise.
- ComboFieldControl.drawTrigger bounds its chevron square by the smaller of the trigger
  strip's own width/height, not height alone, since the strip is no longer close to square
  once the field is much taller than it is wide.
- CardSidebarSections drops the sidebar's old '* 0.55' fixed-width frame on both combo rows;
  each control now sizes itself, and both anchors (card sidebar, board popover) compose the
  narrower field with no other changes needed.
- ComboFieldMetricsTests updated for the new figures, plus a ratio-holds-at-every-size test
  and a rewritten glyph-fit test matching the no-padding rule.

Verification: xcodebuild build succeeded; xcodebuild test -only-testing:KanbanTests — 3220
tests in 559 suites, 3 failures, all PointerLatencyTests (documented locked-screen
environmental mode, confirmed unrelated by isolated rerun). Pixel verification unexercised —
same locked-screen constraint the first pass hit.

Claude-Session: https://claude.ai/code/session_014PtZdPwqZuqEDLc6wZMtEy
This commit is contained in:
2026-08-09 12:06:01 -04:00
parent fda19881de
commit 50e26efdb9
6 changed files with 136 additions and 88 deletions
+31 -8
View File
@@ -223,10 +223,11 @@ struct ComboFieldMetricsTests {
@Test("Every figure scales with the body font, and reproduces the shipped numbers at 13pt")
func metricsAtTheStandardBody() {
let metrics = ComboFieldMetrics.metrics(bodyPointSize: 13)
#expect(metrics.height == 18)
// 2026-08-09 iteration: taller and narrower than the first pass's 18×(whatever the caller
// proposed) bar `width` is now `ComboFieldMetrics`' own figure, not the caller's.
#expect(metrics.height == 36)
#expect(metrics.width == 29)
#expect(metrics.triggerWidth == 16)
#expect(metrics.facePaddingH == 7)
#expect(metrics.facePaddingV == 5)
#expect(metrics.cornerRadius == 3)
#expect(metrics.fieldRadius == 4)
#expect(metrics.triggerInset == 2)
@@ -237,16 +238,32 @@ struct ComboFieldMetricsTests {
let small = ComboFieldMetrics.metrics(bodyPointSize: 11)
let large = ComboFieldMetrics.metrics(bodyPointSize: 24)
#expect(large.height > small.height)
#expect(large.width > small.width)
#expect(large.triggerWidth > small.triggerWidth)
#expect(large.glyphPointSize > small.glyphPointSize)
for metrics in [ComboFieldMetrics.metrics(bodyPointSize: 8), small, large] {
#expect(metrics.height >= 1)
#expect(metrics.width >= 1)
#expect(metrics.triggerWidth >= 1)
#expect(metrics.cornerRadius >= 1)
#expect(metrics.glyphPointSize >= 1)
}
}
/// **The owner's own figure, held still.** "Almost square, about 4:5 ratio" (2026-08-09) is not
/// a one-off measurement `width` is derived from `height` by `widthToHeightRatio`, so this
/// holds at every body size rather than only the one somebody happened to check.
@Test("The field is taller than wide, at about a 4:5 ratio, from height alone")
func fieldIsAlmostSquare() {
for size in [8.0, 11.0, 13.0, 17.0, 24.0, 36.0] as [CGFloat] {
let metrics = ComboFieldMetrics.metrics(bodyPointSize: size)
#expect(metrics.width < metrics.height, "the field should read taller than wide at \(size)pt")
let ratio = metrics.width / metrics.height
#expect(abs(ratio - ComboFieldMetrics.widthToHeightRatio) < 0.05,
"ratio drifted to \(ratio) at \(size)pt, want ~4:5 (0.8)")
}
}
/// The field radius runs a point outside the face's so the two rounded rects stay concentric
/// a small thing, and exactly the kind of thing that drifts when two files own it.
@Test("The field's radius stays outside the face's")
@@ -257,14 +274,20 @@ struct ComboFieldMetricsTests {
}
}
/// A glyph must actually fit: the face's height less its own padding is what the symbol is drawn
/// at, and a negative or vanishing figure is the bug the 14pt height had.
@Test("A glyph fills most of the field's height")
/// A glyph must actually fit, and since the 2026-08-09 iteration removed the face's padding
/// fit *exactly*: `glyphPointSize` is whichever of the face's own two dimensions is smaller, with
/// nothing subtracted for a padding that no longer exists. A negative or vanishing figure is the
/// bug the first pass's 14pt height had.
@Test("A glyph fills its face zone's limiting dimension, with no padding taken out of it")
func glyphFillsTheField() {
for size in [11.0, 13.0, 17.0, 24.0] as [CGFloat] {
let metrics = ComboFieldMetrics.metrics(bodyPointSize: size)
#expect(metrics.glyphPointSize > metrics.height * 0.6, "the glyph is lost in its own field at \(size)pt")
#expect(metrics.glyphPointSize <= metrics.height)
#expect(metrics.glyphPointSize == min(metrics.faceWidth, metrics.height))
// The field is taller than wide by construction, so the face reads the same way and
// `faceWidth` is the dimension actually doing the limiting.
#expect(metrics.faceWidth < metrics.height, "expected the face to be the binding dimension at \(size)pt")
#expect(metrics.glyphPointSize == metrics.faceWidth)
#expect(metrics.glyphPointSize >= 1)
}
}