Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 42 additions & 11 deletions macos/Sources/RustCode/AttentionCards.swift
Original file line number Diff line number Diff line change
Expand Up @@ -44,8 +44,7 @@ struct PermissionCard: View {
.controlSize(.large)
}
.padding(16)
.floatingSurface(in: .rect(cornerRadius: 20))
.overlay { RoundedRectangle(cornerRadius: 20).strokeBorder(Color.attention.opacity(0.5), lineWidth: 1) }
.attentionCard()
}
}

Expand All @@ -60,9 +59,10 @@ struct QuestionCard: View {
var body: some View {
let question = request.question!
VStack(alignment: .leading, spacing: 12) {
// A question is a conversation, not a warning: the brand, not amber.
Label(question.header.isEmpty ? "RustCode has a question" : question.header, systemImage: "questionmark.bubble.fill")
.font(.headline)
.foregroundStyle(Color.attention)
.foregroundStyle(Color.brand)
Text(question.text).textSelection(.enabled)
if question.multiple {
VStack(alignment: .leading, spacing: 6) {
Expand All @@ -74,13 +74,9 @@ struct QuestionCard: View {
}
}
} else {
VStack(alignment: .leading, spacing: 6) {
VStack(alignment: .leading, spacing: 4) {
ForEach(request.answerOptions) { option in
Button { Task { await model.answer(request, with: option) } } label: {
Text(option.name).frame(maxWidth: .infinity, alignment: .leading)
}
.buttonStyle(.bordered)
.controlSize(.large)
AnswerRow(title: option.name) { Task { await model.answer(request, with: option) } }
}
}
}
Expand Down Expand Up @@ -108,7 +104,42 @@ struct QuestionCard: View {
}
}
.padding(16)
.floatingSurface(in: .rect(cornerRadius: 20))
.overlay { RoundedRectangle(cornerRadius: 20).strokeBorder(Color.attention.opacity(0.5), lineWidth: 1) }
.attentionCard()
}
}

/// One choice of a question: a full-width row that lights under the pointer.
private struct AnswerRow: View {
let title: String
let action: () -> Void
@State private var isHovered = false

var body: some View {
Button(action: action) {
HStack {
Text(title).multilineTextAlignment(.leading)
Spacer(minLength: 8)
Image(systemName: "arrow.up.circle.fill")
.foregroundStyle(Color.brand)
.opacity(isHovered ? 1 : 0)
}
.padding(.horizontal, 12)
.padding(.vertical, 9)
.frame(maxWidth: .infinity, alignment: .leading)
.background(isHovered ? AnyShapeStyle(Color.brand.opacity(0.14)) : AnyShapeStyle(.fill.quaternary),
in: .rect(cornerRadius: 12))
.contentShape(.rect(cornerRadius: 12))
}
.buttonStyle(.plain)
.onHover { isHovered = $0 }
}
}

private extension View {
/// An approval or question above the composer: Liquid Glass with a
/// neutral hairline. The header's symbol carries the state, not the edge.
func attentionCard() -> some View {
glassSurface(in: .rect(cornerRadius: 20))
.overlay { RoundedRectangle(cornerRadius: 20).strokeBorder(Color.primary.opacity(0.1), lineWidth: 1) }
}
}
19 changes: 9 additions & 10 deletions macos/Sources/RustCode/ContentView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -32,10 +32,10 @@ struct ContentView: View {
.ignoresSafeArea()
.tint(.brand)
.background(WindowChrome())
.sheet(isPresented: $model.showsSettings) { SettingsView(model: model) }
.sheet(item: $model.renamingSession) { RenameSheet(model: model, session: $0) }
.sheet(item: $model.contextSheet) { ContextSheet(context: $0) }
.sheet(item: $model.commandResult) { CommandResultSheet(result: $0) }
.clickAwayPanel(isPresented: $model.showsSettings) { SettingsView(model: model) }
.clickAwayPanel(item: $model.renamingSession) { RenameSheet(model: model, session: $0) }
.clickAwayPanel(item: $model.contextSheet) { ContextSheet(context: $0) }
.clickAwayPanel(item: $model.commandResult) { CommandResultSheet(result: $0) }
.overlay { ToastStack(model: model) }
}
}
Expand Down Expand Up @@ -359,7 +359,7 @@ struct RenameSheet: View {
let session: SessionSummary
@State private var title: String
@FocusState private var focused: Bool
@Environment(\.dismiss) private var dismiss
@Environment(\.closePanel) private var dismiss

init(model: DesktopModel, session: SessionSummary) {
self.model = model
Expand All @@ -381,15 +381,14 @@ struct RenameSheet: View {
Spacer()
Button("Cancel") { dismiss() }
.buttonStyle(PillButtonStyle())
.keyboardShortcut(.cancelAction)
Button("Rename", action: save)
.buttonStyle(PillButtonStyle(prominent: true))
.keyboardShortcut(.defaultAction)
.disabled(title.trimmingCharacters(in: .whitespaces).isEmpty)
}
}
.padding(20)
.frame(width: 380)
.padding(22)
.frame(width: 420)
.onAppear { focused = true }
}

Expand Down Expand Up @@ -729,7 +728,7 @@ private struct ChatView: View {
}
}
.padding(6)
.frame(width: panel == .model ? 340 : panel == .effort ? 300 : 260, alignment: .leading)
.frame(width: panel == .model ? 380 : panel == .effort ? 330 : 290, alignment: .leading)
.glassSurface(in: .rect(cornerRadius: 20))
if panel == .add { Spacer(minLength: 0) }
}
Expand Down Expand Up @@ -1715,7 +1714,7 @@ struct PopoverRow: View {
}
}
.padding(.horizontal, 10)
.padding(.vertical, 7)
.padding(.vertical, 8)
.frame(maxWidth: .infinity, alignment: .leading)
.background(isHighlighted ? AnyShapeStyle(Color.brand.opacity(0.2))
: isHovered ? AnyShapeStyle(.fill.tertiary) : AnyShapeStyle(.clear), in: .rect(cornerRadius: 14))
Expand Down
6 changes: 3 additions & 3 deletions macos/Sources/RustCode/ContextSheet.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import SwiftUI
/// same breakdown the terminal's context panel shows.
struct ContextSheet: View {
let context: ContextBreakdown
@Environment(\.dismiss) private var dismiss
@Environment(\.closePanel) private var dismiss

/// One colour per category, in the engine's legend order.
static let palette: [Color] = [.blue, .purple, .orange, .teal, .indigo, .pink, .green]
Expand All @@ -32,8 +32,8 @@ struct ContextSheet: View {
.keyboardShortcut(.defaultAction)
}
}
.padding(20)
.frame(width: 440)
.padding(22)
.frame(width: 500)
}

private var header: some View {
Expand Down
62 changes: 62 additions & 0 deletions macos/Sources/RustCode/Panels.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import SwiftUI

private struct ClosePanelKey: EnvironmentKey {
static let defaultValue: @MainActor () -> Void = {}
}

extension EnvironmentValues {
/// Closes the panel a view is shown in. Panels are overlays, not sheets,
/// so `dismiss` does not reach them.
var closePanel: @MainActor () -> Void {
get { self[ClosePanelKey.self] }
set { self[ClosePanelKey.self] = newValue }
}
}

/// A panel over the window, such as Settings: a click on the dimmed window
/// around it or Esc closes it, as a popover does. A sheet ignores clicks
/// outside it.
private struct ClickAwayPanel<Panel: View>: ViewModifier {
let isPresented: Bool
let close: @MainActor () -> Void
@ViewBuilder let panel: () -> Panel

func body(content: Content) -> some View {
content.overlay {
ZStack {
if isPresented {
Color.black.opacity(0.22)
.ignoresSafeArea()
.contentShape(.rect)
.onTapGesture { close() }
.transition(.opacity)
panel()
.environment(\.closePanel, close)
.background(Color.chatBackground)
.clipShape(.rect(cornerRadius: 16))
.overlay { RoundedRectangle(cornerRadius: 16).strokeBorder(Color.primary.opacity(0.08), lineWidth: 1) }
.shadow(color: .black.opacity(0.22), radius: 28, y: 12)
.padding(28)
.background {
// Esc closes the panel from wherever focus is.
Button("Close", action: close).keyboardShortcut(.cancelAction).hidden()
}
.transition(.scale(scale: 0.97).combined(with: .opacity))
}
}
.animation(.snappy(duration: 0.2), value: isPresented)
}
}
}

extension View {
func clickAwayPanel<Panel: View>(isPresented: Binding<Bool>, @ViewBuilder content: @escaping () -> Panel) -> some View {
modifier(ClickAwayPanel(isPresented: isPresented.wrappedValue, close: { isPresented.wrappedValue = false }, panel: content))
}

func clickAwayPanel<Item: Identifiable, Panel: View>(item: Binding<Item?>, @ViewBuilder content: @escaping (Item) -> Panel) -> some View {
modifier(ClickAwayPanel(isPresented: item.wrappedValue != nil, close: { item.wrappedValue = nil }) {
if let value = item.wrappedValue { content(value) }
})
}
}
32 changes: 11 additions & 21 deletions macos/Sources/RustCode/SettingsView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ enum AppearanceSetting {
/// ⌘, — a sheet over the chat: sections on the left, the chosen one on the right.
struct SettingsView: View {
@ObservedObject var model: DesktopModel
@Environment(\.dismiss) private var dismiss
@Environment(\.closePanel) private var dismiss
@StateObject private var capabilities = CapabilitiesStore()
typealias Section = SettingsSection

Expand All @@ -40,15 +40,14 @@ struct SettingsView: View {
}
.padding(12)
.padding(.top, 8)
.frame(width: 200)
.frame(width: 220)
.background(Color.sidebarBackground)

VStack(alignment: .leading, spacing: 0) {
HStack {
Text(section.rawValue).font(.title2.weight(.semibold))
Spacer()
ChromeButton(systemImage: "xmark", help: "Close (Esc)") { dismiss() }
.keyboardShortcut(.cancelAction)
}
.padding(.horizontal, 24)
.padding(.top, 20)
Expand All @@ -70,7 +69,7 @@ struct SettingsView: View {
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topLeading)
.background(Color.chatBackground)
}
.frame(width: 720, height: 480)
.frame(maxWidth: 880, maxHeight: 620)
.tint(.brand)
.task(id: section) {
if section == .skills || section == .mcp { await capabilities.load(model.engineConnection) }
Expand All @@ -87,11 +86,12 @@ private struct SectionRow: View {
var body: some View {
Button(action: action) {
HStack(spacing: 10) {
// A plain symbol, as in the window's sidebar: no coloured tile.
Image(systemName: section.systemImage)
.font(.system(size: 12, weight: .semibold))
.foregroundStyle(.white)
.frame(width: 24, height: 24)
.background(section.tile.gradient, in: .rect(cornerRadius: 6))
.font(.system(size: 14, weight: .medium))
.symbolRenderingMode(.monochrome)
.foregroundStyle(isSelected ? AnyShapeStyle(Color.brand) : AnyShapeStyle(.secondary))
.frame(width: 22, height: 22)
Text(section.rawValue)
Spacer()
}
Expand Down Expand Up @@ -397,21 +397,11 @@ enum SettingsSection: String, CaseIterable, Identifiable {

var systemImage: String {
switch self {
case .general: "gearshape.fill"
case .general: "gearshape"
case .skills: "sparkles"
case .mcp: "point.3.connected.trianglepath.dotted"
case .engine: "terminal.fill"
case .about: "info.circle.fill"
}
}

var tile: Color {
switch self {
case .general: .gray
case .skills: .purple
case .mcp: .teal
case .engine: .orange
case .about: .blue
case .engine: "terminal"
case .about: "info.circle"
}
}
}
4 changes: 2 additions & 2 deletions macos/Sources/RustCode/Toasts.swift
Original file line number Diff line number Diff line change
Expand Up @@ -176,7 +176,7 @@ private struct ToastView: View {
/// A host command's full answer in a sheet, with its Markdown rendered.
struct CommandResultSheet: View {
let result: CommandResult
@Environment(\.dismiss) private var dismiss
@Environment(\.closePanel) private var dismiss

var body: some View {
VStack(alignment: .leading, spacing: 0) {
Expand All @@ -199,6 +199,6 @@ struct CommandResultSheet: View {
.padding(16)
}
}
.frame(minWidth: 520, idealWidth: 620, minHeight: 280, idealHeight: 480)
.frame(maxWidth: 700, maxHeight: 560)
}
}
Loading