summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorChristian Cleberg <[email protected]>2026-07-22 15:02:57 -0500
committerChristian Cleberg <[email protected]>2026-07-22 15:04:44 -0500
commitd3af0e7d51c2bac5de801bfbbed96a105c442e64 (patch)
tree19585d78dc881c756dd0c637dfe6c6fa3fe3a663
parentdd41ed2c51beae07288fdfa724e410e0d321d278 (diff)
downloaddomain-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.
-rw-r--r--DomainDig/ContentView.swift14
-rw-r--r--DomainDig/DomainDigUI.swift56
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