Merge nucleic/bold-thistle-marten-19eu into dev

This commit is contained in:
2026-08-02 03:10:26 -07:00
parent f23b181ad4
commit cf6e1b397a
+43 -14
View File
@@ -9,6 +9,7 @@ struct SessionDetailView: View {
@Environment(AppStore.self) private var store
@Environment(\.appPalette) private var palette
@Environment(\.accessibilityReduceMotion) private var reduceMotion
@Environment(\.colorScheme) private var colorScheme
/// Panel layout for the session's side panels.
@Environment(PanelLayoutStore.self) private var panels
@State private var integrating = false
@@ -1183,6 +1184,20 @@ struct SessionDetailView: View {
}
}
/// Gap between the Intelligence rail and the model·effort caption it resolves to. Negative on
/// purpose: the rail is drawn centered in a 28pt frame sized for its 20pt nub, so it carries
/// ~10pt of its own empty space below the track. Pulling the caption up through that dead
/// space sets the two a few points apart optically, which is what the eye reads as spacing
/// a positive gap here left the pair looking detached. Shared with the reserved footprint
/// below so the invariant height stays exact.
private static let intelligenceCaptionSpacing: CGFloat = -5
/// Type size for the composer's flat mode controls (Auto, Ship, its branch menu). A step down
/// from the row's `.callout`: with the borders and fills gone these read as quiet labels
/// rather than buttons, and the smaller size is what lets the row lose height without the
/// words crowding each other.
private static let composerControlFont: Font = .subheadline
private var autoToggle: some View {
// Nucleic Control chats always run autonomously, so Auto is forced on and the toggle
// is locked there (nvrsion can't function without it). Off-control it's a normal toggle.
@@ -1194,14 +1209,16 @@ struct SessionDetailView: View {
} label: {
Label("Auto", systemImage: on ? "bolt.fill" : "bolt.slash")
.foregroundStyle(on ? composerAccent : Color.secondary)
.padding(.horizontal, 8)
.padding(.vertical, 4)
// Flat and tight (see `composerControlFont`): no fill, no border, and barely any
// vertical padding the glyph and the accent carry the state, and the hit area
// still clears the pointer target with the horizontal inset alone.
.font(Self.composerControlFont)
.padding(.horizontal, 6)
.padding(.vertical, 1)
.contentShape(Rectangle())
}
.buttonStyle(.plain)
.disabled(locked)
.background(AppTheme.composerFill, in: .rect(cornerRadius: 6))
.overlay(RoundedRectangle(cornerRadius: 6).strokeBorder(AppTheme.composerStroke, lineWidth: 1))
.fixedSize()
.help(locked
? lockedHelp
@@ -1241,8 +1258,9 @@ struct SessionDetailView: View {
} label: {
Label("Ship", systemImage: on ? "shippingbox.fill" : "shippingbox")
.foregroundStyle(on ? composerAccent : Color.secondary)
.padding(.horizontal, 8)
.padding(.vertical, 4)
.font(Self.composerControlFont)
.padding(.horizontal, 6)
.padding(.vertical, 1)
.contentShape(Rectangle())
}
.buttonStyle(.plain)
@@ -1266,13 +1284,13 @@ struct SessionDetailView: View {
// nvrsion there is a single shared trunk promoted at the project level, so there's no
// per-session destination to choose show just the Merge toggle, no branch picker.
if on && !nvrsionActive {
Divider().frame(height: 16)
// With the shared border gone, this hairline is the only thing binding Ship to
// the branch it ships into so it stays, trimmed to the shorter row.
Divider().frame(height: 11)
shipDestinationMenu(inherited: inherited, override: override, current: current)
.transition(.move(edge: .leading).combined(with: .opacity))
}
}
.background(AppTheme.composerFill, in: .rect(cornerRadius: 6))
.overlay(RoundedRectangle(cornerRadius: 6).strokeBorder(AppTheme.composerStroke, lineWidth: 1))
.fixedSize()
}
@@ -1306,8 +1324,9 @@ struct SessionDetailView: View {
Text(current)
Image(systemName: "chevron.down").font(.caption2)
}
.padding(.horizontal, 8)
.padding(.vertical, 4)
.font(Self.composerControlFont)
.padding(.horizontal, 6)
.padding(.vertical, 1)
.contentShape(Rectangle())
}
.menuStyle(.button)
@@ -2916,6 +2935,15 @@ struct SessionDetailView: View {
/// Whether there is content and a usable routed destination.
private var canSend: Bool { hasSendableContent && routedRouteAvailable }
/// A little ink in the composer's glass. The bare `.regular` material brightens whatever it
/// samples, so over a dark transcript the card sat a shade lighter than the pane around it and
/// read as a pale bar laid on the chat rather than a pane of it. A black tint settles it back
/// down without touching the refraction. Light mode takes much less the same amount there
/// would be a gray smudge over an already-pale background.
private var composerGlassTint: Color {
.black.opacity(colorScheme == .dark ? 0.3 : 0.08)
}
/// The pane's floating bottom furniture: a pending approval card (when there is one) above the
/// composer, both hovering over the transcript on glass. Measured as one block the height the
/// transcript reserves beneath its last message and anchors its fade to so the two stay in
@@ -3096,7 +3124,7 @@ struct SessionDetailView: View {
ZStack(alignment: .trailing) {
composerIntelligenceControlsFootprint
if intelligenceRoutingActive {
VStack(alignment: .leading, spacing: 1) {
VStack(alignment: .leading, spacing: Self.intelligenceCaptionSpacing) {
IntelligenceSlider(
level: sessionIntelligenceLevelBinding,
orchestraTier: sessionOrchestraTierBinding,
@@ -3158,7 +3186,8 @@ struct SessionDetailView: View {
// Backed by the system Liquid Glass material, exactly like the floating new-chat
// composer: the input area is a card hovering over the conversation, sampling and
// refracting the transcript that scrolls behind it no opaque backing, no divider.
.glassEffect(.regular, in: .rect(cornerRadius: 16))
// Tinted a little darker than the bare material (see `composerGlassTint`).
.glassEffect(.regular.tint(composerGlassTint), in: .rect(cornerRadius: 16))
// The glass renders but doesn't hit-test, so give the card a solid hit shape. It now
// floats over live transcript, so this is load-bearing: without it a click in the gap
// between two controls would land on the message underneath.
@@ -3241,7 +3270,7 @@ struct SessionDetailView: View {
/// Dynamic-Type-aware maximum footprint of the lower control row. Reusing the real route
/// preview keeps this exactly in step with its scaled caption height without a magic constant.
private var composerIntelligenceControlsFootprint: some View {
VStack(alignment: .leading, spacing: 1) {
VStack(alignment: .leading, spacing: Self.intelligenceCaptionSpacing) {
Color.clear.frame(
width: IntelligenceSlider.preferredWidth,
height: IntelligenceSlider.preferredHeight)