mirror of
https://github.com/purrgrammer/grimoire.git
synced 2026-04-09 23:16:50 +02:00
Fix EOSE tracking to prevent state regression after live events
The bug: When events arrived after EOSE was received (live streaming), the subscription state was incorrectly reset from "eose" back to "receiving", causing: - False "LOADING" overall state - Relay UI showing "RECEIVING" spinner when EOSE was already received - Incorrect eoseCount in state machine The fix: Add a new "live" subscription state that represents "EOSE received AND actively receiving live events". State transitions: - waiting → receiving (on first historical event) - receiving → eose (on EOSE signal) - eose → live (on live event after EOSE) Changes: - Add "live" to RelaySubscriptionState type with proper documentation - Add liveCount to ReqOverallState for accurate counting - Update event handler to set "live" state when eoseAt exists - Update EOSE completion check to include "live" state - Update getRelayStateBadge: "live" shows green, "receiving" shows yellow - Update ReqViewer: show pulsing radio icon for live relays - Update tests for new state and color changes https://claude.ai/code/session_01DVTWqKNY4UHVSDDxckjkAh
This commit is contained in:
@@ -1234,12 +1234,17 @@ export default function ReqViewer({
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* EOSE status */}
|
||||
{/* Subscription status icon */}
|
||||
{reqState && (
|
||||
<>
|
||||
{reqState.subscriptionState === "eose" ? (
|
||||
{reqState.subscriptionState === "live" ? (
|
||||
// Live: EOSE received + actively streaming
|
||||
<Radio className="size-3 text-green-500 animate-pulse" />
|
||||
) : reqState.subscriptionState === "eose" ? (
|
||||
// EOSE received, idle
|
||||
<Check className="size-3 text-green-600/70" />
|
||||
) : (
|
||||
// Receiving historical or waiting
|
||||
(reqState.subscriptionState === "receiving" ||
|
||||
reqState.subscriptionState ===
|
||||
"waiting") && (
|
||||
|
||||
@@ -216,10 +216,12 @@ export function useReqTimelineEnhanced(
|
||||
eoseAt: Date.now(),
|
||||
});
|
||||
|
||||
// Check if ALL relays have reached EOSE
|
||||
// Check if ALL relays have reached EOSE (or beyond)
|
||||
// "live" means EOSE was received and we're getting live events
|
||||
const allEose = Array.from(next.values()).every(
|
||||
(s) =>
|
||||
s.subscriptionState === "eose" ||
|
||||
s.subscriptionState === "live" ||
|
||||
s.connectionState === "error" ||
|
||||
s.connectionState === "disconnected",
|
||||
);
|
||||
@@ -268,10 +270,15 @@ export function useReqTimelineEnhanced(
|
||||
lastEventAt: now,
|
||||
});
|
||||
} else {
|
||||
// Update existing relay state
|
||||
// Determine correct subscription state:
|
||||
// - If EOSE already received (eoseAt exists), this is a LIVE event
|
||||
// - Otherwise, we're still receiving historical events
|
||||
const hasEose = state.eoseAt !== undefined;
|
||||
const newSubscriptionState = hasEose ? "live" : "receiving";
|
||||
|
||||
next.set(url, {
|
||||
...state,
|
||||
subscriptionState: "receiving",
|
||||
subscriptionState: newSubscriptionState,
|
||||
eventCount: state.eventCount + 1,
|
||||
firstEventAt: state.firstEventAt ?? now,
|
||||
lastEventAt: now,
|
||||
|
||||
@@ -538,6 +538,7 @@ describe("getStatusText", () => {
|
||||
connectedCount: 3,
|
||||
receivingCount: 2,
|
||||
eoseCount: 1,
|
||||
liveCount: 0,
|
||||
errorCount: 0,
|
||||
disconnectedCount: 0,
|
||||
hasReceivedEvents: true,
|
||||
@@ -568,6 +569,7 @@ describe("getStatusTooltip", () => {
|
||||
connectedCount: 3,
|
||||
receivingCount: 2,
|
||||
eoseCount: 1,
|
||||
liveCount: 0,
|
||||
errorCount: 0,
|
||||
disconnectedCount: 0,
|
||||
hasReceivedEvents: true,
|
||||
@@ -625,7 +627,18 @@ describe("shouldAnimate", () => {
|
||||
});
|
||||
|
||||
describe("getRelayStateBadge", () => {
|
||||
it("should return receiving badge", () => {
|
||||
it("should return live badge (EOSE received + streaming)", () => {
|
||||
const badge = getRelayStateBadge({
|
||||
url: "wss://relay.com",
|
||||
connectionState: "connected",
|
||||
subscriptionState: "live",
|
||||
eventCount: 15,
|
||||
});
|
||||
expect(badge?.text).toBe("LIVE");
|
||||
expect(badge?.color).toBe("text-success");
|
||||
});
|
||||
|
||||
it("should return receiving badge (before EOSE)", () => {
|
||||
const badge = getRelayStateBadge({
|
||||
url: "wss://relay.com",
|
||||
connectionState: "connected",
|
||||
@@ -633,7 +646,8 @@ describe("getRelayStateBadge", () => {
|
||||
eventCount: 5,
|
||||
});
|
||||
expect(badge?.text).toBe("RECEIVING");
|
||||
expect(badge?.color).toBe("text-success");
|
||||
// Warning color because it's still loading (pre-EOSE)
|
||||
expect(badge?.color).toBe("text-warning");
|
||||
});
|
||||
|
||||
it("should return eose badge", () => {
|
||||
|
||||
@@ -35,6 +35,7 @@ export function deriveOverallState(
|
||||
(s) => s.subscriptionState === "receiving",
|
||||
).length;
|
||||
const eoseCount = states.filter((s) => s.subscriptionState === "eose").length;
|
||||
const liveCount = states.filter((s) => s.subscriptionState === "live").length;
|
||||
const errorCount = states.filter((s) => s.connectionState === "error").length;
|
||||
const disconnectedCount = states.filter(
|
||||
(s) => s.connectionState === "disconnected",
|
||||
@@ -55,10 +56,12 @@ export function deriveOverallState(
|
||||
|
||||
const allEoseAt = overallEoseReceived ? Date.now() : undefined;
|
||||
|
||||
// Check if all relays are in terminal states (won't make further progress)
|
||||
// Check if all relays are in terminal states (won't make further progress on initial load)
|
||||
// "live" means EOSE received + streaming, so it's also a terminal state for initial load
|
||||
const allRelaysTerminal = states.every(
|
||||
(s) =>
|
||||
s.subscriptionState === "eose" ||
|
||||
s.subscriptionState === "live" ||
|
||||
s.connectionState === "error" ||
|
||||
s.connectionState === "disconnected",
|
||||
);
|
||||
@@ -143,6 +146,7 @@ export function deriveOverallState(
|
||||
connectedCount,
|
||||
receivingCount,
|
||||
eoseCount,
|
||||
liveCount,
|
||||
errorCount,
|
||||
disconnectedCount,
|
||||
hasReceivedEvents,
|
||||
@@ -242,11 +246,17 @@ export function getRelayStateBadge(
|
||||
): { text: string; color: string } | null {
|
||||
const { subscriptionState, connectionState } = relay;
|
||||
|
||||
// Prioritize subscription state
|
||||
// Prioritize subscription state (order matters for display priority)
|
||||
if (subscriptionState === "live") {
|
||||
// EOSE received, actively receiving live events
|
||||
return { text: "LIVE", color: "text-success" };
|
||||
}
|
||||
if (subscriptionState === "receiving") {
|
||||
return { text: "RECEIVING", color: "text-success" };
|
||||
// Receiving historical events (before EOSE)
|
||||
return { text: "RECEIVING", color: "text-warning" };
|
||||
}
|
||||
if (subscriptionState === "eose") {
|
||||
// EOSE received, idle (no live events yet)
|
||||
return { text: "EOSE", color: "text-info" };
|
||||
}
|
||||
if (subscriptionState === "error") {
|
||||
|
||||
@@ -18,11 +18,23 @@ export type RelayConnectionState =
|
||||
|
||||
/**
|
||||
* Subscription state specific to this REQ
|
||||
*
|
||||
* State machine:
|
||||
* waiting → receiving → eose → live
|
||||
* ↘ ↗
|
||||
* error
|
||||
*
|
||||
* - waiting: Connected, subscription sent, no events yet
|
||||
* - receiving: Getting historical events (before EOSE)
|
||||
* - eose: EOSE received, no live events yet
|
||||
* - live: EOSE received AND receiving live events (streaming mode)
|
||||
* - error: Subscription error occurred
|
||||
*/
|
||||
export type RelaySubscriptionState =
|
||||
| "waiting" // Connected but no events yet
|
||||
| "receiving" // Events being received
|
||||
| "eose" // EOSE received (real or timeout)
|
||||
| "receiving" // Getting historical events (before EOSE)
|
||||
| "eose" // EOSE received, idle (no live events yet)
|
||||
| "live" // EOSE received AND receiving live events
|
||||
| "error"; // Subscription error
|
||||
|
||||
/**
|
||||
@@ -75,7 +87,8 @@ export interface ReqOverallState {
|
||||
totalRelays: number;
|
||||
connectedCount: number;
|
||||
receivingCount: number;
|
||||
eoseCount: number;
|
||||
eoseCount: number; // Relays in "eose" state (EOSE received, idle)
|
||||
liveCount: number; // Relays in "live" state (EOSE received + streaming)
|
||||
errorCount: number;
|
||||
disconnectedCount: number;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user