Transcript Incremental Projection

Nucleic-Session: 0FFD007B-0696-4517-9429-129C7B0FD5AC
Co-authored-by: Nucleic <[email protected]>
This commit is contained in:
2026-07-06 18:49:35 -07:00
co-authored by Nucleic
parent 085a6d5af9
commit 4bd4e81778
@@ -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 {
sessions = live if sessions != live {
cachedSummaries = live sessions = live
persistSummaries(live) cachedSummaries = 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.