Merge nucleic/nimble-velvet-toad-86ur into dev
This commit is contained in:
@@ -50,7 +50,12 @@ enum AppTheme {
|
||||
/// Primary prose color for chat text. Deliberately *not* pure black/white:
|
||||
/// a soft off-white on the deep dark background reads calmly at night instead
|
||||
/// of glowing, and a near-black (not 0) keeps light mode from feeling harsh.
|
||||
static let primaryText = dynamic(
|
||||
static let primaryText = Color(nsColor: primaryTextNSColor)
|
||||
/// The `NSColor` behind ``primaryText``, for AppKit-hosted text that has to match
|
||||
/// SwiftUI-rendered prose exactly — the composer's `NSTextView`, whose default `.labelColor`
|
||||
/// is a full-strength black in light mode and so typed noticeably darker than the transcript
|
||||
/// it sits under.
|
||||
static let primaryTextNSColor = dynamicNSColor(
|
||||
light: NSColor(srgbRed: 0.28, green: 0.28, blue: 0.31, alpha: 1),
|
||||
dark: NSColor(srgbRed: 0.80, green: 0.81, blue: 0.83, alpha: 1),
|
||||
contrastLight: NSColor(srgbRed: 0.06, green: 0.06, blue: 0.08, alpha: 1),
|
||||
@@ -92,6 +97,25 @@ enum AppTheme {
|
||||
dark: .labelColor,
|
||||
contrastLight: NSColor(srgbRed: 0.10, green: 0.10, blue: 0.13, alpha: 1),
|
||||
contrastDark: NSColor(srgbRed: 1, green: 1, blue: 1, alpha: 1))
|
||||
/// Fill behind the in-session composer's chrome: the text field's well, the small bordered
|
||||
/// controls around it (Model / Effort / Auto / Merge), and the queued-message and
|
||||
/// remote-typing pills. Replaces the `.quaternary.opacity(0.5)` these each used to spell out
|
||||
/// — a hierarchical style derived from the window-wide `defaultText`, which in light mode
|
||||
/// resolved to roughly a 10%-alpha near-black and left the whole cluster barely detached from
|
||||
/// the pale detail background. A little more ink gives each control a visible edge without
|
||||
/// making the composer a gray slab. Dark keeps the value it already resolved to.
|
||||
static let composerFill = dynamic(
|
||||
light: NSColor(srgbRed: 0.28, green: 0.28, blue: 0.31, alpha: 0.145),
|
||||
dark: NSColor(white: 1, alpha: 0.10),
|
||||
contrastLight: NSColor(srgbRed: 0.28, green: 0.28, blue: 0.31, alpha: 0.26),
|
||||
contrastDark: NSColor(white: 1, alpha: 0.20))
|
||||
/// The 1pt border drawn around those same composer controls — the bare `.quaternary` beside
|
||||
/// ``composerFill``. Darkened in step with it so the outline keeps leading the fill.
|
||||
static let composerStroke = dynamic(
|
||||
light: NSColor(srgbRed: 0.28, green: 0.28, blue: 0.31, alpha: 0.28),
|
||||
dark: NSColor(white: 1, alpha: 0.20),
|
||||
contrastLight: NSColor(srgbRed: 0.28, green: 0.28, blue: 0.31, alpha: 0.48),
|
||||
contrastDark: NSColor(white: 1, alpha: 0.40))
|
||||
/// The signature orchestra gold — the orchestration mode's accent for the effort pill,
|
||||
/// the composer's animated glow, and the multi-agent transcript cards. A rich amber-gold
|
||||
/// in light mode (dark enough to read on white); a bright, warm gold in dark so it glows
|
||||
@@ -112,13 +136,25 @@ enum AppTheme {
|
||||
contrastLight: NSColor? = nil,
|
||||
contrastDark: NSColor? = nil
|
||||
) -> Color {
|
||||
Color(nsColor: NSColor(name: nil) { appearance in
|
||||
Color(nsColor: dynamicNSColor(
|
||||
light: light, dark: dark, contrastLight: contrastLight, contrastDark: contrastDark))
|
||||
}
|
||||
|
||||
/// ``dynamic`` before the SwiftUI wrapper — for the AppKit views that need the `NSColor`
|
||||
/// itself (see ``primaryTextNSColor``).
|
||||
fileprivate static func dynamicNSColor(
|
||||
light: NSColor,
|
||||
dark: NSColor,
|
||||
contrastLight: NSColor? = nil,
|
||||
contrastDark: NSColor? = nil
|
||||
) -> NSColor {
|
||||
NSColor(name: nil) { appearance in
|
||||
let isDark = AppearanceClassifier.isDark(appearance)
|
||||
if HighContrast.isEnabled, let boosted = isDark ? contrastDark : contrastLight {
|
||||
return boosted
|
||||
}
|
||||
return isDark ? dark : light
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -129,8 +129,11 @@ struct ChatInputField: NSViewRepresentable {
|
||||
textView.isRichText = false
|
||||
textView.allowsUndo = true
|
||||
textView.font = .systemFont(ofSize: NSFont.systemFontSize)
|
||||
textView.textColor = .labelColor
|
||||
textView.insertionPointColor = .labelColor
|
||||
// Match the transcript's prose exactly rather than using `.labelColor`, which is a
|
||||
// full-strength black in light mode — noticeably darker than the messages above, so a
|
||||
// draft read heavier than the conversation it was about to join.
|
||||
textView.textColor = AppTheme.primaryTextNSColor
|
||||
textView.insertionPointColor = AppTheme.primaryTextNSColor
|
||||
textView.drawsBackground = false
|
||||
textView.backgroundColor = .clear
|
||||
textView.textContainerInset = NSSize(width: 0, height: 2)
|
||||
|
||||
@@ -631,8 +631,8 @@ struct SessionDetailView: View {
|
||||
}
|
||||
.menuStyle(.button)
|
||||
.buttonStyle(.plain)
|
||||
.background(.quaternary.opacity(0.5), in: .rect(cornerRadius: 6))
|
||||
.overlay(RoundedRectangle(cornerRadius: 6).strokeBorder(.quaternary, lineWidth: 1))
|
||||
.background(AppTheme.composerFill, in: .rect(cornerRadius: 6))
|
||||
.overlay(RoundedRectangle(cornerRadius: 6).strokeBorder(AppTheme.composerStroke, lineWidth: 1))
|
||||
.fixedSize()
|
||||
.help("Model for the next turn")
|
||||
}
|
||||
@@ -700,9 +700,9 @@ struct SessionDetailView: View {
|
||||
}
|
||||
.menuStyle(.button)
|
||||
.buttonStyle(.plain)
|
||||
.background(.quaternary.opacity(0.5), in: .rect(cornerRadius: 6))
|
||||
.background(AppTheme.composerFill, in: .rect(cornerRadius: 6))
|
||||
.overlay(RoundedRectangle(cornerRadius: 6).strokeBorder(
|
||||
isOrchestra ? AnyShapeStyle(AppTheme.orchestra.opacity(0.6)) : AnyShapeStyle(.quaternary),
|
||||
isOrchestra ? AnyShapeStyle(AppTheme.orchestra.opacity(0.6)) : AnyShapeStyle(AppTheme.composerStroke),
|
||||
lineWidth: 1))
|
||||
.fixedSize()
|
||||
.help(isOrchestra ? ModelCatalog.orchestraBlurb : "Reasoning effort for the next turn")
|
||||
@@ -1145,8 +1145,8 @@ struct SessionDetailView: View {
|
||||
}
|
||||
.buttonStyle(.plain)
|
||||
.disabled(locked)
|
||||
.background(.quaternary.opacity(0.5), in: .rect(cornerRadius: 6))
|
||||
.overlay(RoundedRectangle(cornerRadius: 6).strokeBorder(.quaternary, lineWidth: 1))
|
||||
.background(AppTheme.composerFill, in: .rect(cornerRadius: 6))
|
||||
.overlay(RoundedRectangle(cornerRadius: 6).strokeBorder(AppTheme.composerStroke, lineWidth: 1))
|
||||
.fixedSize()
|
||||
.help(locked
|
||||
? lockedHelp
|
||||
@@ -1216,8 +1216,8 @@ struct SessionDetailView: View {
|
||||
.transition(.move(edge: .leading).combined(with: .opacity))
|
||||
}
|
||||
}
|
||||
.background(.quaternary.opacity(0.5), in: .rect(cornerRadius: 6))
|
||||
.overlay(RoundedRectangle(cornerRadius: 6).strokeBorder(.quaternary, lineWidth: 1))
|
||||
.background(AppTheme.composerFill, in: .rect(cornerRadius: 6))
|
||||
.overlay(RoundedRectangle(cornerRadius: 6).strokeBorder(AppTheme.composerStroke, lineWidth: 1))
|
||||
.fixedSize()
|
||||
}
|
||||
|
||||
@@ -2802,7 +2802,7 @@ struct SessionDetailView: View {
|
||||
? { stepSessionIntelligence($0) } : nil)
|
||||
.frame(height: composerHeight)
|
||||
.padding(8)
|
||||
.background(.quaternary.opacity(0.5), in: .rect(cornerRadius: 8))
|
||||
.background(AppTheme.composerFill, in: .rect(cornerRadius: 8))
|
||||
// Light up the field's border while a file/image drag hovers it.
|
||||
.overlay {
|
||||
if composerDropTargeted {
|
||||
@@ -3029,7 +3029,7 @@ struct SessionDetailView: View {
|
||||
}
|
||||
.padding(.horizontal, 10)
|
||||
.padding(.vertical, 6)
|
||||
.background(.quaternary.opacity(0.5), in: .rect(cornerRadius: 12))
|
||||
.background(AppTheme.composerFill, in: .rect(cornerRadius: 12))
|
||||
.help("Someone is composing a message for this chat on another device. The composer unlocks when they send or stop.")
|
||||
}
|
||||
|
||||
@@ -3072,7 +3072,7 @@ struct SessionDetailView: View {
|
||||
.font(.callout)
|
||||
.padding(.horizontal, 10)
|
||||
.padding(.vertical, 6)
|
||||
.background(.quaternary.opacity(0.5), in: .rect(cornerRadius: 12))
|
||||
.background(AppTheme.composerFill, in: .rect(cornerRadius: 12))
|
||||
.help("This message will send automatically when the agent finishes its current turn.")
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user