fix(chat): clip placeholder to TextEditor bounds and clear it on focus

Two related bugs in the Mac chat composer's placeholder overlay:

* The "Message Hermes… / for commands · drag images to attach" hint had
  no width constraint, so on narrower window geometries it visibly
  overflowed past the rounded TextEditor boundary. Add `lineLimit(1)`,
  `truncationMode(.tail)`, and `frame(maxWidth: .infinity, alignment:
  .leading)` so it ellipsizes inside the field instead.
* The opacity formula `text.isEmpty ? 1 : 0` only hid the placeholder
  once content was typed, not when the field gained focus. Standard
  NSTextField / UITextField semantics clear the placeholder on focus.
  Switch to `(text.isEmpty && !isFocused) ? 1 : 0` so the hint
  disappears the moment the user clicks into the field.

The opaque-background ghosting mitigation from #65 is preserved
unchanged.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Alan Wizemann
2026-05-03 16:47:40 +02:00
parent acd3692faf
commit 34d315793b
@@ -114,7 +114,7 @@ struct RichChatInputBar: View {
// 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:
// or around" ghost. Three mitigations:
//
// 1. Pin an opaque rectangle behind the
// placeholder text. During any single-
@@ -125,15 +125,29 @@ struct RichChatInputBar: View {
// keystroke (removes the per-keystroke
// view-mutation churn the composer was
// already paying for).
// 3. Constrain to a single line with
// `frame(maxWidth: .infinity)` and
// `truncationMode(.tail)` so the long-form
// hint can't escape the rounded
// TextEditor bounds when the sidebar /
// detail-pane geometry compresses the
// composer (was visibly overflowing).
Text(supportsImagePrompts
? "Message Hermes… / for commands · drag images to attach"
: "Message Hermes… / for commands")
.scarfStyle(.body)
.foregroundStyle(ScarfColor.foregroundFaint)
.lineLimit(1)
.truncationMode(.tail)
.frame(maxWidth: .infinity, alignment: .leading)
.padding(.horizontal, 14)
.padding(.vertical, 10)
.background(ScarfColor.backgroundSecondary)
.opacity(text.isEmpty ? 1 : 0)
// Hide once the field has any content OR
// the user is actively focused matches
// standard NSTextField / UITextField
// placeholder semantics.
.opacity((text.isEmpty && !isFocused) ? 1 : 0)
.allowsHitTesting(false)
}
// Drag-drop image attachments. Receives both file URLs