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:
co-authored by
Nucleic
Claude Opus 4.8
parent
9f9fb88672
commit
0e74c7ceb5
@@ -192,6 +192,9 @@ struct ActivityGrid: View {
|
||||
let weekday = cal.component(.weekday, from: today)
|
||||
let startOfWeek = cal.date(byAdding: .day, value: -(weekday - 1), to: today)!
|
||||
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) {
|
||||
ForEach(0..<weeks, id: \.self) { col in
|
||||
VStack(spacing: gap) {
|
||||
@@ -202,7 +205,7 @@ struct ActivityGrid: View {
|
||||
RoundedRectangle(cornerRadius: 2)
|
||||
.fill(isFuture ? Color.clear
|
||||
: 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)
|
||||
}
|
||||
}
|
||||
@@ -210,13 +213,4 @@ struct ActivityGrid: View {
|
||||
}
|
||||
.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
|
||||
// shows that backend's effort range rather than collapsing to one level.
|
||||
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)
|
||||
if let percent = contextPercent {
|
||||
Label("\(percent)%", systemImage: "gauge.with.dots.needle.33percent")
|
||||
|
||||
@@ -44,6 +44,13 @@ enum Palette {
|
||||
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).
|
||||
@@ -240,6 +247,10 @@ struct EffortMenu: View {
|
||||
var modelSKU: String? = nil
|
||||
/// Whether the target project is under Nucleic Control (gates Orchestra).
|
||||
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 sentinel: String { catalog.orchestraSentinelOrFallback }
|
||||
@@ -258,6 +269,9 @@ struct EffortMenu: View {
|
||||
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 {
|
||||
Menu {
|
||||
ForEach(levels, id: \.self) { level in
|
||||
@@ -265,6 +279,7 @@ struct EffortMenu: View {
|
||||
if effort == level { Label(display(level), systemImage: "checkmark") }
|
||||
else { Text(display(level)) }
|
||||
}
|
||||
.disabled(locked)
|
||||
}
|
||||
Divider()
|
||||
Button { effort = sentinel } label: {
|
||||
|
||||
Reference in New Issue
Block a user