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 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: {
|
||||||
|
|||||||
Reference in New Issue
Block a user