From fcfe1c89d6b4cbba4ffe54539c7e87775cb7f019 Mon Sep 17 00:00:00 2001 From: Alan Wizemann Date: Fri, 1 May 2026 15:27:53 +0200 Subject: [PATCH] fix(chat): stop placeholder ghosting in chat composer (#65) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `TextEditor`'s NSTextView surfaces a typed glyph one frame before the SwiftUI binding propagates, so the bare `if text.isEmpty` overlay rendered the translucent placeholder text directly on top of the just-typed character — the "behind or around" ghost the reporter described. Two mitigations: - Pin an opaque `ScarfColor.backgroundSecondary` rect behind the placeholder Text. During any single-frame binding lag the user now sees a clean placeholder rather than layered glyphs. - Switch the conditional to `.opacity(text.isEmpty ? 1 : 0)` so the view tree stays stable per keystroke. Pairs with the composer perf fix from #67. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../Chat/Views/RichChatInputBar.swift | 37 ++++++++++++++----- 1 file changed, 27 insertions(+), 10 deletions(-) diff --git a/scarf/scarf/Features/Chat/Views/RichChatInputBar.swift b/scarf/scarf/Features/Chat/Views/RichChatInputBar.swift index 194c53d..b21c201 100644 --- a/scarf/scarf/Features/Chat/Views/RichChatInputBar.swift +++ b/scarf/scarf/Features/Chat/Views/RichChatInputBar.swift @@ -108,16 +108,33 @@ struct RichChatInputBar: View { ) ) .overlay(alignment: .topLeading) { - if text.isEmpty { - Text(supportsImagePrompts - ? "Message Hermes… / for commands · drag images to attach" - : "Message Hermes… / for commands") - .scarfStyle(.body) - .foregroundStyle(ScarfColor.foregroundFaint) - .padding(.horizontal, 14) - .padding(.vertical, 10) - .allowsHitTesting(false) - } + // Placeholder ghosting (#65): TextEditor's + // NSTextView updates the visible glyphs a frame + // before the SwiftUI binding propagates, so a + // bare `if text.isEmpty` overlay renders the + // translucent placeholder text on top of the + // just-typed character — visible as a "behind + // or around" ghost. Two mitigations: + // + // 1. Pin an opaque rectangle behind the + // placeholder text. During any single- + // frame lag the user sees a clean + // placeholder, never layered glyphs. + // 2. Use `.opacity(...)` instead of an `if`. + // Keeps the view tree stable per + // keystroke (removes the per-keystroke + // view-mutation churn the composer was + // already paying for). + Text(supportsImagePrompts + ? "Message Hermes… / for commands · drag images to attach" + : "Message Hermes… / for commands") + .scarfStyle(.body) + .foregroundStyle(ScarfColor.foregroundFaint) + .padding(.horizontal, 14) + .padding(.vertical, 10) + .background(ScarfColor.backgroundSecondary) + .opacity(text.isEmpty ? 1 : 0) + .allowsHitTesting(false) } // Drag-drop image attachments. Receives both file URLs // (from Finder) and raw image bitmap data (from