Promote nvrsion trunk → dev

Squash-promote the nucleic/trunk work (Sessions Auto-Switching, Play
System Sounds Configuration) into dev. Resolved the two divergent
conflicts where dev and trunk had independently implemented the chat
status-sound feature, keeping trunk's canonical version:

- SettingsView: the Sounds settings live under the General tab (trunk's
  placement); removed dev's duplicate Sounds section from the Chat tab.
- AppStore: adopt trunk's centralized ChatCueState/announceChatCue
  edge-detection (fired from upsertSummary/upsertSummaries), and remove
  dev's superseded chatStatusSound(...) helper and its two hand-placed
  call sites. Also removed a silent merge-duplicated upsertSummaries.

Nucleic-Promote: 1
Co-authored-by: Nucleic <[email protected]>
Co-Authored-By: Claude Opus 4.8 <[email protected]>
This commit is contained in:
2026-06-27 17:14:06 -07:00
co-authored by Nucleic Claude Opus 4.8
parent 9f9fb88672
commit 0e74c7ceb5
3 changed files with 23 additions and 11 deletions
@@ -192,6 +192,9 @@ struct ActivityGrid: View {
let weekday = cal.component(.weekday, from: today) let weekday = cal.component(.weekday, from: today)
let startOfWeek = cal.date(byAdding: .day, value: -(weekday - 1), to: today)! let startOfWeek = cal.date(byAdding: .day, value: -(weekday - 1), to: today)!
let gridStart = cal.date(byAdding: .day, value: -7 * (weeks - 1), to: startOfWeek)! let gridStart = cal.date(byAdding: .day, value: -7 * (weeks - 1), to: startOfWeek)!
// Brightness relative to *this user's* activity, outliers excluded so a few marathon
// days don't crush the everyday range. Computed once for the whole grid (mirrors Mac).
let scale = ActivityScale(activityByDay: activityByDay)
return HStack(alignment: .top, spacing: gap) { return HStack(alignment: .top, spacing: gap) {
ForEach(0..<weeks, id: \.self) { col in ForEach(0..<weeks, id: \.self) { col in
VStack(spacing: gap) { VStack(spacing: gap) {
@@ -202,7 +205,7 @@ struct ActivityGrid: View {
RoundedRectangle(cornerRadius: 2) RoundedRectangle(cornerRadius: 2)
.fill(isFuture ? Color.clear .fill(isFuture ? Color.clear
: isFrozen ? Palette.frozen : isFrozen ? Palette.frozen
: Palette.activity(level: Self.level(activityByDay[date] ?? 0))) : Palette.activity(intensity: scale.intensity(for: activityByDay[date] ?? 0)))
.frame(width: cell, height: cell) .frame(width: cell, height: cell)
} }
} }
@@ -210,13 +213,4 @@ struct ActivityGrid: View {
} }
.frame(maxWidth: .infinity, alignment: .leading) .frame(maxWidth: .infinity, alignment: .leading)
} }
static func level(_ count: Int) -> Int {
switch count {
case 0: 0
case 1...2: 1
case 3...5: 2
default: 3
}
}
} }
@@ -105,7 +105,10 @@ struct SessionDetailView: View {
// No explicit model yet → use the session backend's default, so the menu // No explicit model yet → use the session backend's default, so the menu
// shows that backend's effort range rather than collapsing to one level. // shows that backend's effort range rather than collapsing to one level.
modelSKU: summary.model ?? store.modelCatalog.models(for: summary.backend).first?.sku, modelSKU: summary.model ?? store.modelCatalog.models(for: summary.backend).first?.sku,
controlled: controlled) controlled: controlled,
// Orchestra is a one-way latch for an existing chat — the host won't turn it
// off, so disable the other levels here to match.
lockWhenOrchestra: true)
Spacer(minLength: 4) Spacer(minLength: 4)
if let percent = contextPercent { if let percent = contextPercent {
Label("\(percent)%", systemImage: "gauge.with.dots.needle.33percent") Label("\(percent)%", systemImage: "gauge.with.dots.needle.33percent")
@@ -44,6 +44,13 @@ enum Palette {
default: success default: success
} }
} }
/// Activity-grid shade for a continuous brightness in `0…1` (relative to the user's own
/// activity, outliers excluded — see `ActivityScale`). `0` is an empty day's neutral gray.
/// Mirrors the Mac's `AppPalette.activity(intensity:)`.
static func activity(intensity: Double) -> Color {
intensity <= 0 ? Color.secondary.opacity(0.15) : success.opacity(intensity)
}
} }
/// Status glyph + label, matching the Mac semantics (UX_MACOS §1). /// Status glyph + label, matching the Mac semantics (UX_MACOS §1).
@@ -240,6 +247,10 @@ struct EffortMenu: View {
var modelSKU: String? = nil var modelSKU: String? = nil
/// Whether the target project is under Nucleic Control (gates Orchestra). /// Whether the target project is under Nucleic Control (gates Orchestra).
var controlled: Bool = false var controlled: Bool = false
/// Latch Orchestra on once it's active: for an existing chat the host refuses to turn Orchestra
/// off (`SessionController.setEffort`), so the other levels are disabled to match. Left off for
/// new-chat composition, where the choice isn't committed yet and can still be changed.
var lockWhenOrchestra: Bool = false
private var levels: [String] { catalog.offeredEfforts(forModel: modelSKU) } private var levels: [String] { catalog.offeredEfforts(forModel: modelSKU) }
private var sentinel: String { catalog.orchestraSentinelOrFallback } private var sentinel: String { catalog.orchestraSentinelOrFallback }
@@ -258,6 +269,9 @@ struct EffortMenu: View {
return levels.contains(effort) ? effort : (levels.last ?? MobileEfforts.fallback) return levels.contains(effort) ? effort : (levels.last ?? MobileEfforts.fallback)
} }
/// Once Orchestra is latched on for an existing chat, the ordinary levels can't be chosen.
private var locked: Bool { lockWhenOrchestra && orchestraActive }
var body: some View { var body: some View {
Menu { Menu {
ForEach(levels, id: \.self) { level in ForEach(levels, id: \.self) { level in
@@ -265,6 +279,7 @@ struct EffortMenu: View {
if effort == level { Label(display(level), systemImage: "checkmark") } if effort == level { Label(display(level), systemImage: "checkmark") }
else { Text(display(level)) } else { Text(display(level)) }
} }
.disabled(locked)
} }
Divider() Divider()
Button { effort = sentinel } label: { Button { effort = sentinel } label: {