From 3128d7b0a4f69d4f6304b5bddcb961074e1bca93 Mon Sep 17 00:00:00 2001 From: Brandon McAnsh Date: Sun, 6 Sep 2026 11:30:33 -0400 Subject: [PATCH] fix(chat): give the reply's close button its own ground MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The quote above the composer is now Liquid Glass sampling the transcript, so the dismiss control's contrast moved with whatever message scrolled behind it. A hairline ✕ at `.light` in `textSecondary` was the least dense thing on the bar and had the least to spare. Draw it as `xmark.circle.fill` instead. The disc is a plain translucent fill, not a second glass layer — glass nested in glass muddies the layering, and on a glass platter controls are meant to be drawn on it rather than above it. Palette rendering rather than monochrome, because a monochrome fill knocks the ✕ out as a hole that the transcript then shows through. --- .../Conversation/ComposerReplyStrip.swift | 19 ++++++++++++------- .../FlipcashUI/Theme/Image+Symbols.swift | 3 +++ 2 files changed, 15 insertions(+), 7 deletions(-) diff --git a/Flipcash/Core/Screens/Conversation/ComposerReplyStrip.swift b/Flipcash/Core/Screens/Conversation/ComposerReplyStrip.swift index e787b1869..773d6308f 100644 --- a/Flipcash/Core/Screens/Conversation/ComposerReplyStrip.swift +++ b/Flipcash/Core/Screens/Conversation/ComposerReplyStrip.swift @@ -85,14 +85,19 @@ struct ComposerReplyStrip: View { .accessibilityLabel("Replying to \(target.authorName): \(spokenSnippet)") .accessibilityIdentifier("composer-reply-quote") - // WhatsApp's: a large, thin ✕ rather than a small bold one, sized to sit against two - // lines of quote without crowding them, in a hit target wider than the glyph. + // A disc rather than a bare ✕, because the ground behind it is glass sampling the + // transcript: a hairline glyph's contrast changed with whatever message scrolled past. + // The fill gives it its own ground and takes that variable out. Button(action: onDismiss) { - Image(systemName: SystemSymbol.close.rawValue) - // `.system`, not the app face: the stroke weight is the point of the match, and - // an SF Symbol only takes a weight axis from a system font. - .font(.system(size: 18, weight: .light)) - .foregroundStyle(Color.textSecondary) + Image(systemName: SystemSymbol.closeCircle.rawValue) + // `.system`, not the app face: an SF Symbol only takes its optical axes from a + // system font. + .font(.system(size: 22)) + // Palette, not monochrome. A monochrome fill knocks the ✕ out as a hole, which + // over glass fills with the transcript — the thing the disc is here to stop. + .symbolRenderingMode(.palette) + .foregroundStyle(Color.textMain, Color.textSecondary.opacity(0.35)) + // Wider than the disc: the glyph is small enough to miss on its own. .frame(width: 34, height: 34) .contentShape(.rect) } diff --git a/FlipcashUI/Sources/FlipcashUI/Theme/Image+Symbols.swift b/FlipcashUI/Sources/FlipcashUI/Theme/Image+Symbols.swift index c4b5d1825..3ea67494a 100644 --- a/FlipcashUI/Sources/FlipcashUI/Theme/Image+Symbols.swift +++ b/FlipcashUI/Sources/FlipcashUI/Theme/Image+Symbols.swift @@ -71,6 +71,9 @@ public enum SystemSymbol: String { /// The bare glyph, for dismiss affordances that sit inline with text and /// would be overpowered by the filled app-square `xmark`. case close = "xmark" + /// The dismiss disc, for a control on a glass surface: the fill gives the glyph its + /// own ground rather than leaving it to read against whatever the glass samples. + case closeCircle = "xmark.circle.fill" case pencil = "pencil" case trash = "trash"