fix: G1 context registry — prevent empty contexts from dropping all export events
- Add ReadingContextRegistry for SessionManager→Pipeline context sharing - Wire registry into SessionManager (register on open, unregister on close) - Fix Pipeline to auto-pull from registry when contexts not provided - Fix ScenePhase to not pass empty contexts Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
parent
51b9365ece
commit
f421fbb721
@ -71,14 +71,14 @@ struct AIStudyAppApp: App {
|
|||||||
// Flush events: export → enqueue → (quick upload in background task)
|
// Flush events: export → enqueue → (quick upload in background task)
|
||||||
Task {
|
Task {
|
||||||
let pipeline = ReadingEventUploadPipeline.shared
|
let pipeline = ReadingEventUploadPipeline.shared
|
||||||
pipeline.exportAndEnqueue(contexts: [:]) // contexts populated from active sessions
|
pipeline.exportAndEnqueue() // pulls from ContextRegistry
|
||||||
await pipeline.flush()
|
await pipeline.flush()
|
||||||
}
|
}
|
||||||
case .active:
|
case .active:
|
||||||
// Resume session + reload stale events from Rust
|
// Resume session + reload stale events from Rust
|
||||||
ReadingRuntimeSessionManager.shared.resume()
|
ReadingRuntimeSessionManager.shared.resume()
|
||||||
Task {
|
Task {
|
||||||
ReadingEventUploadPipeline.shared.reloadOnLaunch(contexts: [:])
|
ReadingEventUploadPipeline.shared.reloadOnLaunch()
|
||||||
}
|
}
|
||||||
case .inactive:
|
case .inactive:
|
||||||
break
|
break
|
||||||
|
|||||||
@ -0,0 +1,22 @@
|
|||||||
|
import Foundation
|
||||||
|
|
||||||
|
/// Shared registry of active reading material contexts.
|
||||||
|
/// The SessionManager registers contexts here; the Pipeline reads them for export.
|
||||||
|
@MainActor
|
||||||
|
final class ReadingContextRegistry {
|
||||||
|
static let shared = ReadingContextRegistry()
|
||||||
|
|
||||||
|
private var contexts: [String: ReadingMaterialContext] = [:]
|
||||||
|
|
||||||
|
func register(_ context: ReadingMaterialContext) {
|
||||||
|
contexts[context.materialId] = context
|
||||||
|
}
|
||||||
|
|
||||||
|
func unregister(materialId: String) {
|
||||||
|
contexts.removeValue(forKey: materialId)
|
||||||
|
}
|
||||||
|
|
||||||
|
func allContexts() -> [String: ReadingMaterialContext] {
|
||||||
|
return contexts
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -174,12 +174,13 @@ final class ReadingEventUploadPipeline {
|
|||||||
|
|
||||||
private init() {}
|
private init() {}
|
||||||
|
|
||||||
/// Full pipeline: export from Rust → enqueue.
|
/// Full pipeline: export from Rust → enqueue. Pulls contexts from registry if not provided.
|
||||||
func exportAndEnqueue(contexts: [String: ReadingMaterialContext]) {
|
func exportAndEnqueue(contexts: [String: ReadingMaterialContext] = [:]) {
|
||||||
|
let effectiveContexts = contexts.isEmpty ? ReadingContextRegistry.shared.allContexts() : contexts
|
||||||
let rustEvents = adapter.exportEvents(limit: 100, timestampMs: nowMs())
|
let rustEvents = adapter.exportEvents(limit: 100, timestampMs: nowMs())
|
||||||
guard !rustEvents.isEmpty else { return }
|
guard !rustEvents.isEmpty else { return }
|
||||||
|
|
||||||
let uploadItems = ReadingEventMapper.map(rustEvents: rustEvents, contexts: contexts)
|
let uploadItems = ReadingEventMapper.map(rustEvents: rustEvents, contexts: effectiveContexts)
|
||||||
guard !uploadItems.isEmpty else { return }
|
guard !uploadItems.isEmpty else { return }
|
||||||
|
|
||||||
queue.enqueue(uploadItems)
|
queue.enqueue(uploadItems)
|
||||||
@ -214,7 +215,7 @@ final class ReadingEventUploadPipeline {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Reload on app launch: reload stale Rust events, enqueue, retry failed.
|
/// Reload on app launch: reload stale Rust events, enqueue, retry failed.
|
||||||
func reloadOnLaunch(contexts: [String: ReadingMaterialContext]) {
|
func reloadOnLaunch(contexts: [String: ReadingMaterialContext] = [:]) {
|
||||||
_ = adapter.reloadStaleEvents()
|
_ = adapter.reloadStaleEvents()
|
||||||
_ = adapter.cleanupStaleSessions(nowMs: nowMs(), maxAgeMs: 30 * 60 * 1000)
|
_ = adapter.cleanupStaleSessions(nowMs: nowMs(), maxAgeMs: 30 * 60 * 1000)
|
||||||
|
|
||||||
|
|||||||
@ -52,6 +52,7 @@ final class ReadingRuntimeSessionManager {
|
|||||||
activeSessionId = sessionId
|
activeSessionId = sessionId
|
||||||
activeContext = context
|
activeContext = context
|
||||||
state = .active
|
state = .active
|
||||||
|
ReadingContextRegistry.shared.register(context)
|
||||||
lastPosition = nil
|
lastPosition = nil
|
||||||
lastHeartbeatAtMs = now
|
lastHeartbeatAtMs = now
|
||||||
|
|
||||||
@ -74,6 +75,7 @@ final class ReadingRuntimeSessionManager {
|
|||||||
_ = try? adapter.closeSession(sessionId)
|
_ = try? adapter.closeSession(sessionId)
|
||||||
|
|
||||||
activeSessionId = nil
|
activeSessionId = nil
|
||||||
|
if let ctx = activeContext { ReadingContextRegistry.shared.unregister(materialId: ctx.materialId) }
|
||||||
activeContext = nil
|
activeContext = nil
|
||||||
lastPosition = nil
|
lastPosition = nil
|
||||||
state = .closed
|
state = .closed
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user