The combo halves its height — the symbol picker gives up its padding, not its glyph

The owner's evening review superseded the same day's 4:5-ratio pass: 'reduce
vertical size of both pickers by 50%, on symbol picker by reducing padding.'

ComboFieldMetrics.height goes back to 1.4 em (18pt at the standard body,
exactly half the 4:5 pass's 36) — both combos get this for free, since it's
shared. width stops being height's own restatement (widthToHeightRatio is
gone); it holds the 4:5 pass's pixel figure as its own em number instead,
because re-deriving it at the new height would starve the trigger strip and
empty the face zone entirely.

SymbolComboControl.drawFace now sizes its glyph off the actual face rect it
is handed at draw time rather than the pure ComboFieldMetrics.glyphPointSize
(removed, along with its doc — it only ever described the control's intrinsic
portrait width, not the stretched-wide shape the card sidebar's row actually
proposes). No inset — SF Symbols carry their own margins — with the sizing
and overshoot-scaling rules split into two pure static functions so the fit
is assertable without a live draw.

ComboFieldMetricsTests updated for the new figures and the new actual-rect
glyph rule; the superseded 4:5-ratio-at-six-sizes test is replaced by one
pinning height/width independently and asserting faceWidth stays positive at
every size the old test used.

Claude-Session: https://claude.ai/code/session_014PtZdPwqZuqEDLc6wZMtEy
This commit is contained in:
2026-08-09 17:23:14 -04:00
parent 5da86dcf05
commit cb0af4b95a
3 changed files with 126 additions and 64 deletions
+42 -29
View File
@@ -223,9 +223,10 @@ 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)
// 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)
// Evening 2026-08-09 review: "reduce vertical size of both pickers by 50%" height is half
// the 4:5 pass's 36, and width holds the 4:5 pass's own pixel figure instead of re-deriving
// from the now-shorter height (`ComboFieldMetrics`'s own doc comment).
#expect(metrics.height == 18)
#expect(metrics.width == 29)
#expect(metrics.triggerWidth == 16)
#expect(metrics.cornerRadius == 3)
@@ -240,27 +241,26 @@ struct ComboFieldMetricsTests {
#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() {
/// **The owner's evening figures, held still.** "Reduce vertical size of both pickers by 50%"
/// (2026-08-09 evening) superseded the earlier "almost square, about 4:5 ratio" ruling outright
/// `height` and `width` are now independent em figures rather than one derived from the other, so
/// this asserts both hold at every body size the superseded ratio test used, and that `faceWidth`
/// never collapses the way it would have if `width` had stayed a function of `height`.
@Test("Height is 1.4 em, width is 2.2 em, and the face zone stays positive at every body size")
func heightAndWidthAreIndependentEmFigures() {
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)")
#expect(metrics.height == max(1, (size * 1.4).rounded()), "height drifted from 1.4 em at \(size)pt")
#expect(metrics.width == max(1, (size * 2.2).rounded()), "width drifted from 2.2 em at \(size)pt")
#expect(metrics.faceWidth > 0, "the face zone collapsed at \(size)pt")
}
}
@@ -274,21 +274,34 @@ struct ComboFieldMetricsTests {
}
}
/// 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 == 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)
}
/// **The new actual-rect rule.** `ComboFieldMetrics.glyphPointSize` is gone; `SymbolComboControl`
/// now sizes a glyph off whichever face rect it is actually handed at draw time (this is what lets
/// the sidebar's stretched-wide control still read as "no padding" rather than a small glyph in a
/// big field). Both the point-size rule and the overshoot-scaling rule are pure static functions
/// on `SymbolComboControl`, assertable with no control on screen and no draw.
@Test("A glyph's point size and drawn size follow the actual face rect, not the pure metrics")
func glyphSizesOffTheActualFaceRect() {
// A short, wide rect the shape the card sidebar's row actually proposes now that the field
// is wider than it is tall. The binding dimension is height, not width.
let wideRect = NSRect(x: 0, y: 0, width: 200, height: 18)
#expect(SymbolComboControl.glyphPointSize(forFace: wideRect) == 18)
// A tall, narrow rect, for symmetry the binding dimension flips to width.
let tallRect = NSRect(x: 0, y: 0, width: 12, height: 40)
#expect(SymbolComboControl.glyphPointSize(forFace: tallRect) == 12)
// A glyph configured within its rect never needs to grow.
let snugSize = NSSize(width: 18, height: 18)
#expect(SymbolComboControl.fittedSize(for: snugSize, in: wideRect) == snugSize)
// A horizontally elongated glyph (wider than the point size it was configured at) overshoots
// the rect on its long axis and must be scaled down proportionally, not clipped.
let elongated = NSSize(width: 36, height: 18)
let fitted = SymbolComboControl.fittedSize(for: elongated, in: wideRect)
#expect(fitted.width <= wideRect.width)
#expect(fitted.height <= wideRect.height)
#expect(abs(fitted.width / fitted.height - elongated.width / elongated.height) < 0.001,
"the scale-down must preserve the glyph's own aspect ratio")
}
/// **The two controls are the same control.** Both are `ComboFieldControl`s and both take their