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.
|
/// or a representative one. No-op in demo, which seeds the aggregate directly.
|
||||||
private func rebuildAggregate() {
|
private func rebuildAggregate() {
|
||||||
guard !demoMode else { return }
|
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()
|
let live = aggregatedSessions()
|
||||||
if !live.isEmpty {
|
if !live.isEmpty {
|
||||||
|
if sessions != live {
|
||||||
sessions = live
|
sessions = live
|
||||||
cachedSummaries = live
|
cachedSummaries = live
|
||||||
persistSummaries(live)
|
persistSummaries(live)
|
||||||
|
}
|
||||||
} else if connections.values.contains(where: { $0.connectivity.isLive }) {
|
} else if connections.values.contains(where: { $0.connectivity.isLive }) {
|
||||||
// Connected, but the host genuinely has no sessions — reflect that honestly.
|
// Connected, but the host genuinely has no sessions — reflect that honestly.
|
||||||
sessions = []
|
setIfChanged(\.sessions, [])
|
||||||
} else {
|
} else {
|
||||||
// Offline: keep showing the saved history rather than blanking the list.
|
// Offline: keep showing the saved history rather than blanking the list.
|
||||||
sessions = cachedSummaries
|
setIfChanged(\.sessions, cachedSummaries)
|
||||||
}
|
}
|
||||||
dashboard = DashboardSnapshot.merged(connections.values.map(\.dashboard))
|
setIfChanged(\.dashboard, DashboardSnapshot.merged(connections.values.map(\.dashboard)))
|
||||||
meshPeers = connections.values.flatMap(\.meshPeers)
|
setIfChanged(\.meshPeers, connections.values.flatMap(\.meshPeers))
|
||||||
|
|
||||||
let ctx = contextConnection
|
let ctx = contextConnection
|
||||||
hostName = ctx?.hostName ?? ""
|
setIfChanged(\.hostName, ctx?.hostName ?? "")
|
||||||
capabilities = ctx?.capabilities
|
setIfChanged(\.capabilities, ctx?.capabilities
|
||||||
?? WireCapabilities(canModifyToolInput: false, allowAlwaysScopes: [])
|
?? WireCapabilities(canModifyToolInput: false, allowAlwaysScopes: []))
|
||||||
modelCatalog = ctx?.modelCatalog ?? .empty
|
setIfChanged(\.modelCatalog, ctx?.modelCatalog ?? .empty)
|
||||||
|
|
||||||
if let id = openSessionHostID, let conn = connections[id] {
|
if let id = openSessionHostID, let conn = connections[id] {
|
||||||
// While a transcript is open, the composer/controls act on *that* Mac — its connectivity
|
// While a transcript is open, the composer/controls act on *that* Mac — its connectivity
|
||||||
// gates send and its scope drives the control affordances.
|
// gates send and its scope drives the control affordances.
|
||||||
connectivity = conn.connectivity
|
setIfChanged(\.connectivity, conn.connectivity)
|
||||||
grantedScope = conn.grantedScope
|
setIfChanged(\.grantedScope, conn.grantedScope)
|
||||||
} else {
|
} else {
|
||||||
connectivity = aggregateConnectivity()
|
setIfChanged(\.connectivity, aggregateConnectivity())
|
||||||
// Optimistic new-chat gating: enabled if *any* Mac grants control (the owning Mac still
|
// Optimistic new-chat gating: enabled if *any* Mac grants control (the owning Mac still
|
||||||
// enforces scope when the intent lands there).
|
// enforces scope when the intent lands there).
|
||||||
grantedScope = connections.values
|
setIfChanged(\.grantedScope, connections.values
|
||||||
.filter { $0.connectivity.isLive }.map(\.grantedScope).max() ?? .approve
|
.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
|
/// 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
|
/// 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.
|
/// screen without duplicating what's shown or dropping history outside the host's tail window.
|
||||||
|
|||||||
Reference in New Issue
Block a user