composer changes

This commit is contained in:
2026-07-05 19:47:27 -07:00
parent eb7a0da106
commit 6bcf8444e7
@@ -18,7 +18,7 @@ struct StartChatComposer: View {
/// project page's "+", so the chat always lands in that project.
var lockedProject: WireProject? = nil
@State private var projectID: ProjectID?
@State private var auto = false
@State private var autoOverride: Bool?
@State private var model: String?
@State private var effort = MobileEfforts.fallback
// Advanced options (the Mac new-session sheet's base branch + worktree fields).
@@ -31,6 +31,11 @@ struct StartChatComposer: View {
lockedProject ?? (projects.first { $0.id == projectID } ?? projects.first)
}
private var controlled: Bool { selected?.isNucleicControlled ?? false }
/// Auto-approve for the chat about to start: the user's choice off-control, but forced on for a
/// Nucleic Control project. The host locks Auto on for those chats regardless of what the phone
/// sends (they run autonomously — nvrsion needs it: see `AppStore.createSession`), so the
/// composer shows it locked on to match the Mac rather than offering a toggle that wouldn't take.
private var effectiveAuto: Bool { controlled || (autoOverride ?? false) }
var body: some View {
VStack(alignment: .leading, spacing: 10) {
@@ -51,12 +56,17 @@ struct StartChatComposer: View {
.font(.subheadline).foregroundStyle(.secondary)
}
Spacer()
Toggle(isOn: $auto) {
Label("Auto", systemImage: auto ? "bolt.fill" : "bolt.slash")
Toggle(isOn: Binding(get: { effectiveAuto }, set: { autoOverride = $0 })) {
Label("Auto", systemImage: effectiveAuto ? "bolt.fill" : "bolt.slash")
}
.toggleStyle(.button)
.tint(Palette.accent)
.font(.caption)
// Locked on for a Nucleic Control project — those chats always run autonomously.
.disabled(controlled)
.help(controlled
? "Auto-approve is always on for Nucleic Control chats — they run autonomously."
: "Auto-approve safe actions; destructive ones still ask.")
}
HStack {
ModelMenu(model: $model, catalog: store.modelCatalog, backend: nil)
@@ -104,7 +114,7 @@ struct StartChatComposer: View {
store.startChat(
in: project.id, message: draft, model: model, effort: effort,
baseBranch: branch.isEmpty ? nil : branch,
useWorktree: useWorktree, auto: auto)
useWorktree: useWorktree, auto: effectiveAuto)
draft = ""
onStart?()
}
@@ -123,6 +133,10 @@ struct StartChatComposer: View {
.padding(.horizontal, 14)
.padding(.vertical, 12)
.glassSurface(cornerRadius: 26)
// Prefill the base branch with the project's default so it actually appears (mirroring the
// Mac composer's `effectiveBranch`), rather than sitting as an empty placeholder.
.onAppear(perform: syncToProject)
.onChange(of: projectID) { _, _ in syncToProject() }
// Switching to a model with a lower effort cap can't leave an unsupported level selected.
.onChange(of: model) { _, newModel in
let levels = store.modelCatalog.offeredEfforts(forModel: newModel)
@@ -132,6 +146,16 @@ struct StartChatComposer: View {
}
}
}
/// Sync the composer to the freshly-selected project: show its default branch as the base, and
/// resolve a carried-over Orchestra selection down to a real level when the new project isn't
/// under Nucleic Control (Orchestra can't run there — mirrors the Mac's `effectiveEffort`).
private func syncToProject() {
baseBranch = selected?.defaultBranch ?? ""
if MobileEfforts.isOrchestra(effort), !controlled {
effort = store.modelCatalog.offeredEfforts(forModel: model).last ?? MobileEfforts.fallback
}
}
}
/// The floating "+" start-chat affordance: a Liquid Glass circle in the lower-right that reveals