diff options
| author | Christian Cleberg <[email protected]> | 2026-07-22 15:02:57 -0500 |
|---|---|---|
| committer | Christian Cleberg <[email protected]> | 2026-07-22 15:04:44 -0500 |
| commit | d3af0e7d51c2bac5de801bfbbed96a105c442e64 (patch) | |
| tree | 19585d78dc881c756dd0c637dfe6c6fa3fe3a663 /DomainDig | |
| parent | dd41ed2c51beae07288fdfa724e410e0d321d278 (diff) | |
| download | domain-dig-d3af0e7d51c2bac5de801bfbbed96a105c442e64.tar.gz domain-dig-d3af0e7d51c2bac5de801bfbbed96a105c442e64.tar.bz2 domain-dig-d3af0e7d51c2bac5de801bfbbed96a105c442e64.zip | |
fix(a11y): stop section-header trailing controls letter-wrapping
Reported on device: the Domain section header's Note button rendered
vertically — "N o t e", one character per line in a screen-tall capsule —
at a larger (not even accessibility-tier) text size. Same pathology the
row badges had: text inside a squeezed HStack compresses to a
one-character column instead of the layout adapting.
Three-part fix, mirroring the proven row treatment:
- CollapsibleSectionView's header is now a ViewThatFits: title, trailing
controls, and chevron on one line while they genuinely fit; otherwise
the trailing controls drop below the title row. Applies to every
section header, not just Domain.
- The Note and Track bordered buttons get .fixedSize() so their text can
never letter-wrap — their natural width is what pushes the header onto
its stacked layout.
- The "Tracked" pill becomes an icon-only indicator (eye in a tinted
circle) — with Pin and Note beside it the full pill was the first thing
to compress, and the word survives for VoiceOver via its label.
Enforced audit suite stays green (7 tests, 0 failures, floor runtime).
The post-lookup header state itself is not reachable by the harness — it
requires a live lookup — so on-device confirmation closes this out.
Diffstat (limited to 'DomainDig')
| -rw-r--r-- | DomainDig/ContentView.swift | 14 | ||||
| -rw-r--r-- | DomainDig/DomainDigUI.swift | 56 |
2 files changed, 54 insertions, 16 deletions
diff --git a/DomainDig/ContentView.swift b/DomainDig/ContentView.swift index ea6ead8..103e3b5 100644 --- a/DomainDig/ContentView.swift +++ b/DomainDig/ContentView.swift @@ -1352,7 +1352,16 @@ struct DomainSectionView: View { CollapsibleSectionView(title: "Domain", isCollapsed: $isCollapsed) { if let trackedDomain { HStack(spacing: 8) { - AppStatusBadgeView(model: .init(title: "Tracked", systemImage: "eye.fill", foregroundColor: Color(.statusPositive), backgroundColor: Color(.statusPositiveSurface))) + // Icon-only: the header also carries Pin and Note, and the + // full "Tracked" pill compresses at larger text sizes. + // VoiceOver still hears the word via the label. + Image(systemName: "eye.fill") + .font(appDensity.font(.caption)) + .foregroundStyle(Color(.statusPositive)) + .padding(6) + .background(Color(.statusPositiveSurface), in: Circle()) + .fixedSize() + .accessibilityLabel("Tracked") Button { onTogglePinned() } label: { @@ -1369,6 +1378,8 @@ struct DomainSectionView: View { } .buttonStyle(.bordered) .font(appDensity.font(.caption)) + // Never compress into a vertical letter column. + .fixedSize() } } } else { @@ -1378,6 +1389,7 @@ struct DomainSectionView: View { } .buttonStyle(.bordered) .font(appDensity.font(.caption)) + .fixedSize() } } content: { CardView(allowsHorizontalScroll: false) { diff --git a/DomainDig/DomainDigUI.swift b/DomainDig/DomainDigUI.swift index 67c30b4..d0ef40b 100644 --- a/DomainDig/DomainDigUI.swift +++ b/DomainDig/DomainDigUI.swift @@ -446,23 +446,26 @@ struct CollapsibleSectionView<HeaderTrailing: View, Content: View>: View { isCollapsed.toggle() } } label: { - HStack(alignment: .center, spacing: 10) { - VStack(alignment: .leading, spacing: 3) { - Text(title) - .font(appDensity.font(.headline, weight: .semibold)) - .foregroundStyle(.primary) - if let subtitle { - Text(subtitle) - .font(appDensity.font(.caption)) - .foregroundStyle(Color(.appTextSecondary)) + // One line while the title, trailing controls, and chevron + // genuinely fit; otherwise the trailing controls drop below the + // title. Without this, a squeezed trailing button letter-wraps + // vertically ("N o t e" as a screen-tall capsule) at larger + // Dynamic Type sizes — same pathology as the row badges. + ViewThatFits(in: .horizontal) { + HStack(alignment: .center, spacing: 10) { + titleBlock + Spacer(minLength: 8) + trailing() + chevron + } + VStack(alignment: .leading, spacing: 8) { + HStack(alignment: .center, spacing: 10) { + titleBlock + Spacer(minLength: 8) + chevron } + trailing() } - Spacer(minLength: 8) - trailing() - Image(systemName: isCollapsed ? "chevron.down" : "chevron.up") - .font(.caption.weight(.semibold)) - .foregroundStyle(Color(.appTextSecondary)) - .accessibilityHidden(true) } .contentShape(Rectangle()) .frame(minHeight: appDensity.metrics.controlMinHeight, alignment: .center) @@ -482,6 +485,29 @@ struct CollapsibleSectionView<HeaderTrailing: View, Content: View>: View { } } } + + private var titleBlock: some View { + VStack(alignment: .leading, spacing: 3) { + Text(title) + .font(appDensity.font(.headline, weight: .semibold)) + .foregroundStyle(.primary) + .fixedSize(horizontal: false, vertical: true) + .multilineTextAlignment(.leading) + if let subtitle { + Text(subtitle) + .font(appDensity.font(.caption)) + .foregroundStyle(Color(.appTextSecondary)) + .fixedSize(horizontal: false, vertical: true) + } + } + } + + private var chevron: some View { + Image(systemName: isCollapsed ? "chevron.down" : "chevron.up") + .font(.caption.weight(.semibold)) + .foregroundStyle(Color(.appTextSecondary)) + .accessibilityHidden(true) + } } /// A horizontally scrolling row of read-only tag chips, e.g. for a tracked |
