refactor(client): keep pending interaction change UI-scoped

This commit is contained in:
Turtle
2026-08-04 20:28:23 +08:00
parent 76d43d616d
commit e2049910d5
18 changed files with 53 additions and 175 deletions
+11 -8
View File
@@ -219,14 +219,17 @@ export function apply(ctx: Context): void {
console.error('[web-runtime] history reconnect failed:', error)
}
},
onDisconnected: () => {
// Reconnect replays flow from stream open, ahead of onConnected, so each
// generation death is the only safe moment to drop generation-owned state.
sessions.handleDisconnected()
try {
sessionHistory.handleDisconnected()
} catch (error) {
console.error('[web-runtime] history disconnect failed:', error)
onStateChange: (state) => {
// Generation death fires before any next-generation frame can arrive
// (reconnect replays flow from stream open, ahead of onConnected):
// the only safe moment to drop generation-scoped interaction state.
if (state === 'reconnecting') {
sessions.handleDisconnected()
try {
sessionHistory.handleDisconnected()
} catch (error) {
console.error('[web-runtime] history disconnect failed:', error)
}
}
},
})
@@ -791,9 +791,8 @@ export class SessionManager {
* send no frame, so stale statuses and buffered answerable frames must not
* survive into the next generation — mux-open replay re-adds every still-pending
* request with its live rpcId.
*/
*/
handleDisconnected(): void {
for (const session of this.sessions.values()) session.handleDisconnected()
if (this.pendingInteractions.size > 0) {
this.pendingInteractions.clear()
this.notifier.markDirty()
@@ -393,9 +393,8 @@ export class Session implements SessionFace {
}
/** Reconnect rebuild (manager calls this on onConnected for instances that were opened):
* reset the window and rerun open. Pending waits reset at generation death, before the next
* baseline replay can arrive, so this method preserves freshly replayed waits. Invalidates
* any in-flight open first — its history request rode the dead connection and must not settle
* reset the window and rerun open; pending waits for the baseline replay. Invalidates any
* in-flight open first — its history request rode the dead connection and must not settle
* the fresh generation into 'error' (audit S4). */
async resync(): Promise<void> {
// The queue mirror is NOT cleared here: onConnected (which drives resync)
@@ -411,6 +410,10 @@ export class Session implements SessionFace {
this.events = []
this.views = []
this.baseSeq = 0
// Superseded, not settled: the baseline replay re-sends still-pending requested frames verbatim
// (same rpcId), re-minting fresh waits; a stale reference's respond() still reaches the host.
this.pending.clear()
this.pendingRev++
this.subscribedLastSeq = null
this.liveBuffer = []
this.notifier.markDirty()
@@ -439,16 +442,6 @@ export class Session implements SessionFace {
// ---- Manager-only entry points (@internal; never called by the UI) ----
/** Discard generation-scoped waits before a next-generation replay can arrive. */
handleDisconnected(): void {
if (this.pending.size === 0) return
// Superseded, not settled: a stale reference may already be responding,
// and the replay re-mints each still-pending request with the same rpcId.
this.pending.clear()
this.pendingRev++
this.notifier.markDirty()
}
/**
* Mux frame arrival (the dispatch switch).
* @param rpcId - the frame envelope id (the respond backfill key for requested frames).