Transcript Incremental Projection
Nucleic-Session: 0FFD007B-0696-4517-9429-129C7B0FD5AC Co-authored-by: Nucleic <[email protected]>
This commit is contained in:
@@ -725,41 +725,54 @@ final class RemoteStore: ObservableObject {
|
||||
/// or a representative one. No-op in demo, which seeds the aggregate directly.
|
||||
private func rebuildAggregate() {
|
||||
guard !demoMode else { return }
|
||||
// Every assignment below goes through `setIfChanged`: `didUpdate` fires on *every* wire
|
||||
// frame from *any* host (session churn, dashboard refresh, diff-stat ticks), and a plain
|
||||
// `@Published` assignment fires `objectWillChange` even when the value is identical —
|
||||
// re-evaluating every view observing the store for nothing. Equality checks over these
|
||||
// small aggregates are far cheaper than a whole-tree SwiftUI invalidation.
|
||||
let live = aggregatedSessions()
|
||||
if !live.isEmpty {
|
||||
sessions = live
|
||||
cachedSummaries = live
|
||||
persistSummaries(live)
|
||||
if sessions != live {
|
||||
sessions = live
|
||||
cachedSummaries = live
|
||||
persistSummaries(live)
|
||||
}
|
||||
} else if connections.values.contains(where: { $0.connectivity.isLive }) {
|
||||
// Connected, but the host genuinely has no sessions — reflect that honestly.
|
||||
sessions = []
|
||||
setIfChanged(\.sessions, [])
|
||||
} else {
|
||||
// Offline: keep showing the saved history rather than blanking the list.
|
||||
sessions = cachedSummaries
|
||||
setIfChanged(\.sessions, cachedSummaries)
|
||||
}
|
||||
dashboard = DashboardSnapshot.merged(connections.values.map(\.dashboard))
|
||||
meshPeers = connections.values.flatMap(\.meshPeers)
|
||||
setIfChanged(\.dashboard, DashboardSnapshot.merged(connections.values.map(\.dashboard)))
|
||||
setIfChanged(\.meshPeers, connections.values.flatMap(\.meshPeers))
|
||||
|
||||
let ctx = contextConnection
|
||||
hostName = ctx?.hostName ?? ""
|
||||
capabilities = ctx?.capabilities
|
||||
?? WireCapabilities(canModifyToolInput: false, allowAlwaysScopes: [])
|
||||
modelCatalog = ctx?.modelCatalog ?? .empty
|
||||
setIfChanged(\.hostName, ctx?.hostName ?? "")
|
||||
setIfChanged(\.capabilities, ctx?.capabilities
|
||||
?? WireCapabilities(canModifyToolInput: false, allowAlwaysScopes: []))
|
||||
setIfChanged(\.modelCatalog, ctx?.modelCatalog ?? .empty)
|
||||
|
||||
if let id = openSessionHostID, let conn = connections[id] {
|
||||
// While a transcript is open, the composer/controls act on *that* Mac — its connectivity
|
||||
// gates send and its scope drives the control affordances.
|
||||
connectivity = conn.connectivity
|
||||
grantedScope = conn.grantedScope
|
||||
setIfChanged(\.connectivity, conn.connectivity)
|
||||
setIfChanged(\.grantedScope, conn.grantedScope)
|
||||
} else {
|
||||
connectivity = aggregateConnectivity()
|
||||
setIfChanged(\.connectivity, aggregateConnectivity())
|
||||
// Optimistic new-chat gating: enabled if *any* Mac grants control (the owning Mac still
|
||||
// enforces scope when the intent lands there).
|
||||
grantedScope = connections.values
|
||||
.filter { $0.connectivity.isLive }.map(\.grantedScope).max() ?? .approve
|
||||
setIfChanged(\.grantedScope, connections.values
|
||||
.filter { $0.connectivity.isLive }.map(\.grantedScope).max() ?? .approve)
|
||||
}
|
||||
}
|
||||
|
||||
/// Assign a `@Published` property only when the value actually differs, so a no-op rebuild
|
||||
/// doesn't fire `objectWillChange` (and with it a whole-tree view re-evaluation).
|
||||
private func setIfChanged<T: Equatable>(_ keyPath: ReferenceWritableKeyPath<RemoteStore, T>, _ value: T) {
|
||||
if self[keyPath: keyPath] != value { self[keyPath: keyPath] = value }
|
||||
}
|
||||
|
||||
/// Merge transcript events by `seq` (monotonic, globally unique within a session), keeping the
|
||||
/// union sorted. Lets a reconnect's snapshot fold its events into the transcript already on
|
||||
/// screen without duplicating what's shown or dropping history outside the host's tail window.
|
||||
|
||||
Reference in New Issue
Block a user