Merge nucleic/clever-lunar-newt-tfwt into dev
This commit is contained in:
@@ -22,7 +22,7 @@ enum AppTheme {
|
|||||||
/// capsule drowns out the status dot and icons in the row; a gray lets that
|
/// capsule drowns out the status dot and icons in the row; a gray lets that
|
||||||
/// per-row iconography read through while still marking the active chat.
|
/// per-row iconography read through while still marking the active chat.
|
||||||
static let selection = dynamic(
|
static let selection = dynamic(
|
||||||
light: NSColor(srgbRed: 155.0 / 255.0, green: 158.0 / 255.0, blue: 165.0 / 255.0, alpha: 1),
|
light: NSColor(srgbRed: 0.86, green: 0.865, blue: 0.88, alpha: 1),
|
||||||
dark: NSColor(srgbRed: 0.34, green: 0.35, blue: 0.38, alpha: 1))
|
dark: NSColor(srgbRed: 0.34, green: 0.35, blue: 0.38, alpha: 1))
|
||||||
/// Subtle hairline for borders/dividers.
|
/// Subtle hairline for borders/dividers.
|
||||||
static let hairline = dynamic(
|
static let hairline = dynamic(
|
||||||
@@ -41,8 +41,14 @@ enum AppTheme {
|
|||||||
/// a soft off-white on the deep dark background reads calmly at night instead
|
/// 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.
|
/// of glowing, and a near-black (not 0) keeps light mode from feeling harsh.
|
||||||
static let primaryText = dynamic(
|
static let primaryText = dynamic(
|
||||||
light: NSColor(srgbRed: 0.24, green: 0.24, blue: 0.27, alpha: 1),
|
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))
|
dark: NSColor(srgbRed: 0.80, green: 0.81, blue: 0.83, alpha: 1))
|
||||||
|
/// Default label color for views that do not choose a semantic foreground explicitly.
|
||||||
|
/// Light mode uses the same softened near-black as prose; dark mode delegates to AppKit's
|
||||||
|
/// label color so applying this at the window root does not alter the existing dark theme.
|
||||||
|
static let defaultText = dynamic(
|
||||||
|
light: NSColor(srgbRed: 0.28, green: 0.28, blue: 0.31, alpha: 1),
|
||||||
|
dark: .labelColor)
|
||||||
/// The signature orchestra gold — the orchestration mode's accent for the effort pill,
|
/// 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
|
/// 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
|
/// in light mode (dark enough to read on white); a bright, warm gold in dark so it glows
|
||||||
@@ -259,6 +265,13 @@ struct AppPalette: Equatable {
|
|||||||
/// the AI sidebar segment's snowflake/glow.
|
/// the AI sidebar segment's snowflake/glow.
|
||||||
var frozen: Color
|
var frozen: Color
|
||||||
var activityBase: Color
|
var activityBase: Color
|
||||||
|
/// Higher-contrast text variants for the small categorical log lines in a transcript.
|
||||||
|
/// Fill/status colors can stay vivid in light mode, but those same bright values wash out
|
||||||
|
/// against the transcript background when used as text (amber is the clearest offender).
|
||||||
|
/// Their dark-mode side deliberately matches the existing night-softened palette.
|
||||||
|
var logAttention: Color
|
||||||
|
var logSuccess: Color
|
||||||
|
var logDanger: Color
|
||||||
|
|
||||||
static func make(_ mode: ColorVisionMode, controlled: Bool = false) -> AppPalette {
|
static func make(_ mode: ColorVisionMode, controlled: Bool = false) -> AppPalette {
|
||||||
// Cached so the resolved (dark-softened) colors keep a stable identity
|
// Cached so the resolved (dark-softened) colors keep a stable identity
|
||||||
@@ -296,40 +309,58 @@ struct AppPalette: Equatable {
|
|||||||
private static func base(_ mode: ColorVisionMode) -> AppPalette {
|
private static func base(_ mode: ColorVisionMode) -> AppPalette {
|
||||||
switch mode {
|
switch mode {
|
||||||
case .standard:
|
case .standard:
|
||||||
AppPalette(
|
let attention = Color(red: 1.00, green: 0.65, blue: 0.15)
|
||||||
|
let success = Color(red: 0.30, green: 0.80, blue: 0.45)
|
||||||
|
let danger = Color(red: 0.92, green: 0.34, blue: 0.34)
|
||||||
|
return AppPalette(
|
||||||
accent: Color(red: 0.04, green: 0.52, blue: 0.50), // deep teal — 4.5:1 on white text
|
accent: Color(red: 0.04, green: 0.52, blue: 0.50), // deep teal — 4.5:1 on white text
|
||||||
neutral: .gray,
|
neutral: .gray,
|
||||||
active: Color(red: 0.20, green: 0.55, blue: 0.95), // blue
|
active: Color(red: 0.20, green: 0.55, blue: 0.95), // blue
|
||||||
attention: Color(red: 1.00, green: 0.65, blue: 0.15), // amber
|
attention: attention, // amber
|
||||||
success: Color(red: 0.30, green: 0.80, blue: 0.45), // green
|
success: success, // green
|
||||||
paused: Color(red: 0.62, green: 0.49, blue: 0.93), // purple
|
paused: Color(red: 0.62, green: 0.49, blue: 0.93), // purple
|
||||||
danger: Color(red: 0.92, green: 0.34, blue: 0.34), // red
|
danger: danger, // red
|
||||||
frozen: Color(red: 0.26, green: 0.64, blue: 0.96), // cold "iced" blue
|
frozen: Color(red: 0.26, green: 0.64, blue: 0.96), // cold "iced" blue
|
||||||
activityBase: Color(red: 0.30, green: 0.80, blue: 0.45))
|
activityBase: Color(red: 0.30, green: 0.80, blue: 0.45),
|
||||||
|
logAttention: textTone(light: Color(red: 0.61, green: 0.36, blue: 0.03), darkSource: attention),
|
||||||
|
logSuccess: textTone(light: Color(red: 0.12, green: 0.47, blue: 0.24), darkSource: success),
|
||||||
|
logDanger: textTone(light: Color(red: 0.70, green: 0.18, blue: 0.20), darkSource: danger))
|
||||||
case .deuteranopia, .protanopia:
|
case .deuteranopia, .protanopia:
|
||||||
// Okabe–Ito-derived: avoids relying on red↔green separation.
|
// Okabe–Ito-derived: avoids relying on red↔green separation.
|
||||||
AppPalette(
|
let attention = Color(red: 0.90, green: 0.62, blue: 0.00)
|
||||||
|
let success = Color(red: 0.34, green: 0.71, blue: 0.91)
|
||||||
|
let danger = Color(red: 0.84, green: 0.37, blue: 0.00)
|
||||||
|
return AppPalette(
|
||||||
accent: Color(red: 0.00, green: 0.50, blue: 0.58), // deep teal — 4.5:1 on white text
|
accent: Color(red: 0.00, green: 0.50, blue: 0.58), // deep teal — 4.5:1 on white text
|
||||||
neutral: .gray,
|
neutral: .gray,
|
||||||
active: Color(red: 0.00, green: 0.45, blue: 0.70), // blue
|
active: Color(red: 0.00, green: 0.45, blue: 0.70), // blue
|
||||||
attention: Color(red: 0.90, green: 0.62, blue: 0.00), // orange
|
attention: attention, // orange
|
||||||
success: Color(red: 0.34, green: 0.71, blue: 0.91), // sky blue
|
success: success, // sky blue
|
||||||
paused: Color(red: 0.80, green: 0.47, blue: 0.65), // reddish purple
|
paused: Color(red: 0.80, green: 0.47, blue: 0.65), // reddish purple
|
||||||
danger: Color(red: 0.84, green: 0.37, blue: 0.00), // vermillion
|
danger: danger, // vermillion
|
||||||
frozen: Color(red: 0.20, green: 0.58, blue: 0.86), // cool sky blue
|
frozen: Color(red: 0.20, green: 0.58, blue: 0.86), // cool sky blue
|
||||||
activityBase: Color(red: 0.00, green: 0.45, blue: 0.70))
|
activityBase: Color(red: 0.00, green: 0.45, blue: 0.70),
|
||||||
|
logAttention: textTone(light: Color(red: 0.56, green: 0.35, blue: 0.00), darkSource: attention),
|
||||||
|
logSuccess: textTone(light: Color(red: 0.02, green: 0.39, blue: 0.59), darkSource: success),
|
||||||
|
logDanger: textTone(light: Color(red: 0.64, green: 0.25, blue: 0.00), darkSource: danger))
|
||||||
case .tritanopia:
|
case .tritanopia:
|
||||||
// Blue↔yellow weak; red↔green is intact, so lean on those plus magenta.
|
// Blue↔yellow weak; red↔green is intact, so lean on those plus magenta.
|
||||||
AppPalette(
|
let attention = Color(red: 0.85, green: 0.20, blue: 0.46)
|
||||||
|
let success = Color(red: 0.00, green: 0.45, blue: 0.70)
|
||||||
|
let danger = Color(red: 0.79, green: 0.10, blue: 0.15)
|
||||||
|
return AppPalette(
|
||||||
accent: Color(red: 0.46, green: 0.33, blue: 0.78), // indigo-violet
|
accent: Color(red: 0.46, green: 0.33, blue: 0.78), // indigo-violet
|
||||||
neutral: .gray,
|
neutral: .gray,
|
||||||
active: Color(red: 0.00, green: 0.62, blue: 0.45), // green-teal
|
active: Color(red: 0.00, green: 0.62, blue: 0.45), // green-teal
|
||||||
attention: Color(red: 0.85, green: 0.20, blue: 0.46), // magenta
|
attention: attention, // magenta
|
||||||
success: Color(red: 0.00, green: 0.45, blue: 0.70), // blue
|
success: success, // blue
|
||||||
paused: Color(red: 0.67, green: 0.36, blue: 0.66), // purple
|
paused: Color(red: 0.67, green: 0.36, blue: 0.66), // purple
|
||||||
danger: Color(red: 0.79, green: 0.10, blue: 0.15), // red
|
danger: danger, // red
|
||||||
frozen: Color(red: 0.30, green: 0.72, blue: 0.85), // light cyan (kept bright to stay legible)
|
frozen: Color(red: 0.30, green: 0.72, blue: 0.85), // light cyan (kept bright to stay legible)
|
||||||
activityBase: Color(red: 0.00, green: 0.45, blue: 0.70))
|
activityBase: Color(red: 0.00, green: 0.45, blue: 0.70),
|
||||||
|
logAttention: textTone(light: Color(red: 0.63, green: 0.12, blue: 0.33), darkSource: attention),
|
||||||
|
logSuccess: textTone(light: Color(red: 0.00, green: 0.38, blue: 0.59), darkSource: success),
|
||||||
|
logDanger: textTone(light: Color(red: 0.66, green: 0.07, blue: 0.12), darkSource: danger))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -347,7 +378,10 @@ struct AppPalette: Equatable {
|
|||||||
paused: AppPalette.nightSoft(paused),
|
paused: AppPalette.nightSoft(paused),
|
||||||
danger: AppPalette.nightSoft(danger),
|
danger: AppPalette.nightSoft(danger),
|
||||||
frozen: AppPalette.nightSoft(frozen),
|
frozen: AppPalette.nightSoft(frozen),
|
||||||
activityBase: AppPalette.nightSoft(activityBase))
|
activityBase: AppPalette.nightSoft(activityBase),
|
||||||
|
logAttention: logAttention,
|
||||||
|
logSuccess: logSuccess,
|
||||||
|
logDanger: logDanger)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Light mode keeps `base`; dark mode gets a desaturated, dimmed variant.
|
/// Light mode keeps `base`; dark mode gets a desaturated, dimmed variant.
|
||||||
@@ -364,6 +398,23 @@ struct AppPalette: Equatable {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A text color that is deliberately darker in light mode while retaining the categorical
|
||||||
|
/// palette's existing softened color in dark mode.
|
||||||
|
private static func textTone(light: Color, darkSource: Color) -> Color {
|
||||||
|
guard let lightNS = NSColor(light).usingColorSpace(.sRGB),
|
||||||
|
let sourceNS = NSColor(darkSource).usingColorSpace(.sRGB)
|
||||||
|
else { return light }
|
||||||
|
var h: CGFloat = 0, s: CGFloat = 0, b: CGFloat = 0, a: CGFloat = 0
|
||||||
|
sourceNS.getHue(&h, saturation: &s, brightness: &b, alpha: &a)
|
||||||
|
let dark = NSColor(hue: h,
|
||||||
|
saturation: s * 0.68,
|
||||||
|
brightness: min(b, 0.80) * 0.82,
|
||||||
|
alpha: a)
|
||||||
|
return Color(nsColor: NSColor(name: nil) { appearance in
|
||||||
|
AppearanceClassifier.isDark(appearance) ? dark : lightNS
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
/// The dot/accent color for a session's status, refined by the last turn's
|
/// The dot/accent color for a session's status, refined by the last turn's
|
||||||
/// disposition: a finished-the-work turn reads as "done" (success) rather than the
|
/// disposition: a finished-the-work turn reads as "done" (success) rather than the
|
||||||
/// calm "ready for you" accent used when the agent is genuinely waiting on you.
|
/// calm "ready for you" accent used when the agent is genuinely waiting on you.
|
||||||
|
|||||||
@@ -70,10 +70,12 @@ struct BuildInfo {
|
|||||||
/// locally-built / pre-prod copies, canary-yellow for canary, blue for beta, gold for a release candidate. The
|
/// locally-built / pre-prod copies, canary-yellow for canary, blue for beta, gold for a release candidate. The
|
||||||
/// leading edge names the channel; the commit hash and an all-caps "EXPERIMENTAL"
|
/// leading edge names the channel; the commit hash and an all-caps "EXPERIMENTAL"
|
||||||
/// badge sit on the trailing
|
/// badge sit on the trailing
|
||||||
/// edge. The tint is kept translucent so it reads as a quiet marker rather than
|
/// edge. Light mode uses a deeper, denser channel color so the strip does not wash out against
|
||||||
/// fighting the window background. Renders nothing for a shipping App Store release.
|
/// the pale window; dark mode keeps the quieter translucent treatment. Renders nothing for a
|
||||||
|
/// shipping App Store release.
|
||||||
struct BuildBanner: View {
|
struct BuildBanner: View {
|
||||||
var info: BuildInfo = .current
|
var info: BuildInfo = .current
|
||||||
|
@Environment(\.colorScheme) private var colorScheme
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
if let style = Style(channel: info.channel) {
|
if let style = Style(channel: info.channel) {
|
||||||
@@ -102,7 +104,8 @@ struct BuildBanner: View {
|
|||||||
// — so the *toolbar* is the glassy header and this is just its colored
|
// — so the *toolbar* is the glassy header and this is just its colored
|
||||||
// backdrop. Giving the strip its own `glassEffect` drew a bright glass edge on
|
// backdrop. Giving the strip its own `glassEffect` drew a bright glass edge on
|
||||||
// it; the toolbar's own glass is pinned visible in `RootView` instead.
|
// it; the toolbar's own glass is pinned visible in `RootView` instead.
|
||||||
.background(style.tint.opacity(0.5))
|
.background(style.tint(for: colorScheme)
|
||||||
|
.opacity(colorScheme == .light ? 0.88 : 0.5))
|
||||||
.help(style.tooltip(version: info.version, build: info.buildLabel))
|
.help(style.tooltip(version: info.version, build: info.buildLabel))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -110,6 +113,7 @@ struct BuildBanner: View {
|
|||||||
/// Per-channel appearance. `nil` for `.release` so the banner disappears entirely.
|
/// Per-channel appearance. `nil` for `.release` so the banner disappears entirely.
|
||||||
private struct Style {
|
private struct Style {
|
||||||
var tint: Color
|
var tint: Color
|
||||||
|
var lightTint: Color
|
||||||
var label: String
|
var label: String
|
||||||
var icon: String
|
var icon: String
|
||||||
// Most channels share the default glyph size; the canary bell-with-waves
|
// Most channels share the default glyph size; the canary bell-with-waves
|
||||||
@@ -120,19 +124,23 @@ struct BuildBanner: View {
|
|||||||
switch channel {
|
switch channel {
|
||||||
case .local:
|
case .local:
|
||||||
tint = Color(red: 0.78, green: 0.18, blue: 0.18) // red
|
tint = Color(red: 0.78, green: 0.18, blue: 0.18) // red
|
||||||
|
lightTint = Color(red: 0.66, green: 0.12, blue: 0.12)
|
||||||
label = "Local Development Build"
|
label = "Local Development Build"
|
||||||
icon = "hammer.fill"
|
icon = "hammer.fill"
|
||||||
case .canary:
|
case .canary:
|
||||||
tint = Color(red: 1.00, green: 0.87, blue: 0.00) // bright canary yellow
|
tint = Color(red: 1.00, green: 0.87, blue: 0.00) // bright canary yellow
|
||||||
|
lightTint = Color(red: 0.48, green: 0.32, blue: 0.02)
|
||||||
label = "Canary Build"
|
label = "Canary Build"
|
||||||
icon = "bell.and.waves.left.and.right.fill"
|
icon = "bell.and.waves.left.and.right.fill"
|
||||||
iconSize = 13
|
iconSize = 13
|
||||||
case .beta:
|
case .beta:
|
||||||
tint = Color(red: 0.13, green: 0.40, blue: 0.86) // blue
|
tint = Color(red: 0.13, green: 0.40, blue: 0.86) // blue
|
||||||
|
lightTint = Color(red: 0.08, green: 0.29, blue: 0.68)
|
||||||
label = "Public Beta"
|
label = "Public Beta"
|
||||||
icon = "person.3.fill"
|
icon = "person.3.fill"
|
||||||
case .releaseCandidate:
|
case .releaseCandidate:
|
||||||
tint = Color(red: 0.83, green: 0.65, blue: 0.12) // gold
|
tint = Color(red: 0.83, green: 0.65, blue: 0.12) // gold
|
||||||
|
lightTint = Color(red: 0.48, green: 0.30, blue: 0.02)
|
||||||
label = "Release Candidate"
|
label = "Release Candidate"
|
||||||
icon = "checkmark.seal.fill"
|
icon = "checkmark.seal.fill"
|
||||||
case .release:
|
case .release:
|
||||||
@@ -140,6 +148,10 @@ struct BuildBanner: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func tint(for colorScheme: ColorScheme) -> Color {
|
||||||
|
colorScheme == .light ? lightTint : tint
|
||||||
|
}
|
||||||
|
|
||||||
func tooltip(version: String?, build: String) -> String {
|
func tooltip(version: String?, build: String) -> String {
|
||||||
let v = version.map { "\($0) " } ?? ""
|
let v = version.map { "\($0) " } ?? ""
|
||||||
return "\(label) — \(v)commit \(build). Not a release build."
|
return "\(label) — \(v)commit \(build). Not a release build."
|
||||||
|
|||||||
@@ -323,7 +323,7 @@ enum SyntaxTheme {
|
|||||||
|
|
||||||
/// Shared across every mode — neutral, already-safe roles.
|
/// Shared across every mode — neutral, already-safe roles.
|
||||||
private static let plain = dynamic(
|
private static let plain = dynamic(
|
||||||
light: NSColor(srgbRed: 0.13, green: 0.13, blue: 0.15, alpha: 1),
|
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))
|
dark: NSColor(srgbRed: 0.80, green: 0.81, blue: 0.83, alpha: 1))
|
||||||
private static let comment = dynamic(
|
private static let comment = dynamic(
|
||||||
light: NSColor(srgbRed: 0.42, green: 0.48, blue: 0.52, alpha: 1),
|
light: NSColor(srgbRed: 0.42, green: 0.48, blue: 0.52, alpha: 1),
|
||||||
|
|||||||
@@ -316,6 +316,9 @@ struct RootView: View {
|
|||||||
// opaque fill, which is why its identical sidebar material reads as clear glass.
|
// opaque fill, which is why its identical sidebar material reads as clear glass.
|
||||||
.background { WindowTranslucencyConfigurator() }
|
.background { WindowTranslucencyConfigurator() }
|
||||||
.preferredColorScheme(appearance.colorScheme)
|
.preferredColorScheme(appearance.colorScheme)
|
||||||
|
// Soften the default near-black used by unstyled labels and sidebar titles in light mode.
|
||||||
|
// Explicit semantic/categorical foreground styles below still take precedence.
|
||||||
|
.foregroundStyle(AppTheme.defaultText)
|
||||||
.dynamicTypeSize(textSize.dynamicTypeSize)
|
.dynamicTypeSize(textSize.dynamicTypeSize)
|
||||||
.tint(palette.accent)
|
.tint(palette.accent)
|
||||||
.environment(panels)
|
.environment(panels)
|
||||||
@@ -1399,6 +1402,7 @@ private extension SessionSummary {
|
|||||||
|
|
||||||
struct SessionRow: View {
|
struct SessionRow: View {
|
||||||
@Environment(\.appPalette) private var palette
|
@Environment(\.appPalette) private var palette
|
||||||
|
@Environment(\.colorScheme) private var colorScheme
|
||||||
let summary: SessionSummary
|
let summary: SessionSummary
|
||||||
var isSelected: Bool = false
|
var isSelected: Bool = false
|
||||||
/// Blocked in the `LockManager` queue — the status dot becomes an hourglass so the
|
/// Blocked in the `LockManager` queue — the status dot becomes an hourglass so the
|
||||||
@@ -1427,6 +1431,16 @@ struct SessionRow: View {
|
|||||||
/// Nil under a remote project header (the header's globe already says it) and for local rows.
|
/// Nil under a remote project header (the header's globe already says it) and for local rows.
|
||||||
var remoteHostName: String? = nil
|
var remoteHostName: String? = nil
|
||||||
|
|
||||||
|
/// The dark selection still needs light glyphs. The much softer light selection lets each
|
||||||
|
/// glyph keep its normal semantic color instead of turning into a low-contrast white mark.
|
||||||
|
private var selectionNeutral: Color {
|
||||||
|
isSelected && colorScheme == .dark ? .white : .secondary
|
||||||
|
}
|
||||||
|
|
||||||
|
private var selectionAccent: Color {
|
||||||
|
isSelected && colorScheme == .dark ? .white : palette.accent
|
||||||
|
}
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
HStack(spacing: 6) {
|
HStack(spacing: 6) {
|
||||||
if isWaitingForLock {
|
if isWaitingForLock {
|
||||||
@@ -1483,18 +1497,16 @@ struct SessionRow: View {
|
|||||||
// A chat running on another device — a peer Mac or a Covalence Cloud runner
|
// A chat running on another device — a peer Mac or a Covalence Cloud runner
|
||||||
// — folded under this Mac's section for the same project (every device holds
|
// — folded under this Mac's section for the same project (every device holds
|
||||||
// the same projects, so the header is shared and the row says where it runs).
|
// the same projects, so the header is shared and the row says where it runs).
|
||||||
// White on the selection highlight so it stays legible.
|
|
||||||
Image(systemName: "globe").font(.system(size: 8))
|
Image(systemName: "globe").font(.system(size: 8))
|
||||||
.foregroundStyle(isSelected ? Color.white : .secondary)
|
.foregroundStyle(selectionNeutral)
|
||||||
.help("Runs on \(foldedHost) — mirrored live over the mesh")
|
.help("Runs on \(foldedHost) — mirrored live over the mesh")
|
||||||
.accessibilityLabel("On \(foldedHost)")
|
.accessibilityLabel("On \(foldedHost)")
|
||||||
}
|
}
|
||||||
if movedToName == nil, summary.auto {
|
if movedToName == nil, summary.auto {
|
||||||
// Against the mid-gray selection highlight the saturated teal
|
// The deep dark-mode selection needs white; the subtle light-mode
|
||||||
// accent nearly disappears; switch to high-contrast white when
|
// selection leaves the semantic accent legible as-is.
|
||||||
// the row is selected so the bolt stays legible.
|
|
||||||
Image(systemName: "bolt.fill").font(.system(size: 8))
|
Image(systemName: "bolt.fill").font(.system(size: 8))
|
||||||
.foregroundStyle(isSelected ? Color.white : palette.accent)
|
.foregroundStyle(selectionAccent)
|
||||||
}
|
}
|
||||||
if movedToName == nil, isOrchestraActive {
|
if movedToName == nil, isOrchestraActive {
|
||||||
// Orchestration mode marker, sat beside the Auto bolt. Always the
|
// Orchestration mode marker, sat beside the Auto bolt. Always the
|
||||||
@@ -1511,11 +1523,11 @@ struct SessionRow: View {
|
|||||||
// and nothing's happened since — a glanceable marker, sat directly
|
// and nothing's happened since — a glanceable marker, sat directly
|
||||||
// beside the Auto bolt. Cleared once a new turn lands, or by a
|
// beside the Auto bolt. Cleared once a new turn lands, or by a
|
||||||
// later failed/conflict autoship (which never lights this). White
|
// later failed/conflict autoship (which never lights this). White
|
||||||
// on the selection highlight so it stays legible. Suppressed when
|
// on the dark selection highlight so it stays legible. Suppressed when
|
||||||
// autoship failed or a conflict is pending (the warning below takes
|
// autoship failed or a conflict is pending (the warning below takes
|
||||||
// precedence).
|
// precedence).
|
||||||
Image(systemName: "shippingbox.fill").font(.system(size: 8))
|
Image(systemName: "shippingbox.fill").font(.system(size: 8))
|
||||||
.foregroundStyle(isSelected ? Color.white : palette.accent)
|
.foregroundStyle(selectionAccent)
|
||||||
.accessibilityLabel("Autoship ran")
|
.accessibilityLabel("Autoship ran")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1544,10 +1556,11 @@ struct SessionRow: View {
|
|||||||
}
|
}
|
||||||
if movedToName == nil, summary.unseenCompletion {
|
if movedToName == nil, summary.unseenCompletion {
|
||||||
// Unread dot: the chat finished its work and the user hasn't opened it
|
// Unread dot: the chat finished its work and the user hasn't opened it
|
||||||
// yet. Cleared the moment they do. White on the selection highlight so
|
// yet. Cleared the moment they do. It keeps the semantic accent on the soft
|
||||||
// it stays legible (the row can't be both selected and unread for long,
|
// light selection and switches to white on the deeper dark selection (the row
|
||||||
// but selection can land before the open-driven clear).
|
// can't be both selected and unread for long, but selection can land before the
|
||||||
Circle().fill(isSelected ? Color.white : palette.accent)
|
// open-driven clear).
|
||||||
|
Circle().fill(selectionAccent)
|
||||||
.frame(width: 7, height: 7)
|
.frame(width: 7, height: 7)
|
||||||
.accessibilityLabel("Unread — completed")
|
.accessibilityLabel("Unread — completed")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -72,6 +72,7 @@ struct SettingsView: View {
|
|||||||
// flexible.
|
// flexible.
|
||||||
.frame(minWidth: 840, idealWidth: 840, maxWidth: 840, minHeight: 560, maxHeight: .infinity)
|
.frame(minWidth: 840, idealWidth: 840, maxWidth: 840, minHeight: 560, maxHeight: .infinity)
|
||||||
.preferredColorScheme(appearance.colorScheme)
|
.preferredColorScheme(appearance.colorScheme)
|
||||||
|
.foregroundStyle(AppTheme.defaultText)
|
||||||
.dynamicTypeSize(textSize.dynamicTypeSize)
|
.dynamicTypeSize(textSize.dynamicTypeSize)
|
||||||
.background(SettingsWindowConfigurator(reapplyToken: (selection ?? .general).rawValue))
|
.background(SettingsWindowConfigurator(reapplyToken: (selection ?? .general).rawValue))
|
||||||
.onAppear { if selection == nil { selection = SettingsPane(rawValue: storedPane) ?? .general } }
|
.onAppear { if selection == nil { selection = SettingsPane(rawValue: storedPane) ?? .general } }
|
||||||
|
|||||||
@@ -737,7 +737,7 @@ struct TranscriptRow: View, Equatable {
|
|||||||
finished: true,
|
finished: true,
|
||||||
worktreeRoot: store.openSession?.worktreePath)
|
worktreeRoot: store.openSession?.worktreePath)
|
||||||
case .approvalRequested(let request):
|
case .approvalRequested(let request):
|
||||||
label("pause.circle", "Approval requested: \(request.title)", palette.attention)
|
label("pause.circle", "Approval requested: \(request.title)", palette.logAttention)
|
||||||
case .approvalResolved(let resolved):
|
case .approvalResolved(let resolved):
|
||||||
resolvedByLabel(resolved.decidedBy)
|
resolvedByLabel(resolved.decidedBy)
|
||||||
case .usage(let usage):
|
case .usage(let usage):
|
||||||
@@ -761,13 +761,13 @@ struct TranscriptRow: View, Equatable {
|
|||||||
switch finished.outcome {
|
switch finished.outcome {
|
||||||
case .interrupted:
|
case .interrupted:
|
||||||
// A deliberate manual stop, not a failure: stop-sign octagon in amber.
|
// A deliberate manual stop, not a failure: stop-sign octagon in amber.
|
||||||
label("octagon", "Run stopped", palette.attention)
|
label("octagon", "Run stopped", palette.logAttention)
|
||||||
case .errored:
|
case .errored:
|
||||||
label("exclamationmark.triangle", "Run errored", palette.danger)
|
label("exclamationmark.triangle", "Run errored", palette.logDanger)
|
||||||
case .completed:
|
case .completed:
|
||||||
label("flag.checkered", "Run completed", palette.success)
|
label("flag.checkered", "Run completed", palette.logSuccess)
|
||||||
case .maxTurns:
|
case .maxTurns:
|
||||||
label("flag.checkered", "Run maxTurns", palette.success)
|
label("flag.checkered", "Run maxTurns", palette.logSuccess)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
case .error(let error):
|
case .error(let error):
|
||||||
@@ -777,7 +777,7 @@ struct TranscriptRow: View, Equatable {
|
|||||||
if Self.isAuthError(error.message) {
|
if Self.isAuthError(error.message) {
|
||||||
AuthErrorRow(rawMessage: error.message, onLogin: onLogin)
|
AuthErrorRow(rawMessage: error.message, onLogin: onLogin)
|
||||||
} else {
|
} else {
|
||||||
label("exclamationmark.triangle", error.message, palette.danger)
|
label("exclamationmark.triangle", error.message, palette.logDanger)
|
||||||
}
|
}
|
||||||
case .note(let note) where note.vmMonitor:
|
case .note(let note) where note.vmMonitor:
|
||||||
// A "VM opened" note reads as a one-click prompt to surface the monitor panel for this
|
// A "VM opened" note reads as a one-click prompt to surface the monitor panel for this
|
||||||
|
|||||||
Reference in New Issue
Block a user