mirror of
https://github.com/purrgrammer/grimoire.git
synced 2026-04-08 06:27:17 +02:00
ui: improve relay tooltip, update docs
This commit is contained in:
@@ -29,6 +29,12 @@ Grimoire is a Nostr protocol explorer and developer tool. It's a tiling window m
|
||||
- Maintains failure counts, backoff states, last success/failure times
|
||||
- Prevents repeated connection attempts to dead relays
|
||||
|
||||
**Nostr Query State Machine** (`src/lib/req-state-machine.ts` + `src/hooks/useReqTimelineEnhanced.ts`):
|
||||
- Accurate tracking of REQ subscriptions across multiple relays
|
||||
- Distinguishes between `LIVE`, `LOADING`, `PARTIAL`, `OFFLINE`, `CLOSED`, and `FAILED` states
|
||||
- Solves "LIVE with 0 relays" bug by tracking per-relay connection state and event counts
|
||||
- Pattern: Subscribe to relays individually to detect per-relay EOSE and errors
|
||||
|
||||
**Critical**: Don't create new EventStore, RelayPool, or RelayLiveness instances - use the singletons in `src/services/`
|
||||
|
||||
### Window System
|
||||
|
||||
@@ -19,9 +19,32 @@ We'll combine two sources of truth:
|
||||
|
||||
This hybrid approach avoids duplicate subscriptions while providing accurate status tracking.
|
||||
|
||||
## Implementation Tasks
|
||||
## Implementation Progress
|
||||
|
||||
### Phase 1: Core Infrastructure
|
||||
### COMPLETED: Phase 1: Core Infrastructure
|
||||
- [x] Task 1.1: Create Per-Relay State Tracking Types (`src/types/req-state.ts`)
|
||||
- [x] Task 1.2: Create State Derivation Logic (`src/lib/req-state-machine.ts`)
|
||||
- [x] Task 1.3: Create Enhanced Timeline Hook (`src/hooks/useReqTimelineEnhanced.ts`)
|
||||
- [x] Unit tests for state machine (`src/lib/req-state-machine.test.ts`)
|
||||
|
||||
### COMPLETED: Phase 2: UI Integration
|
||||
- [x] Task 2.1: Update ReqViewer Status Indicator with 8-state machine
|
||||
- [x] Task 2.2: Enhance Relay Dropdown with Per-Relay Status and 2-column grid tooltip
|
||||
- [x] Task 2.3: Add Empty/Error States (Failed, Offline, Partial)
|
||||
|
||||
### PENDING: Phase 3: Testing & Polish
|
||||
- [ ] Task 3.1: Add Unit Tests for `useReqTimelineEnhanced` hook
|
||||
- [ ] Task 3.2: Add Integration Tests for `ReqViewer` UI
|
||||
- [ ] Task 3.3: Complete Manual Testing Checklist
|
||||
|
||||
### FUTURE: Phase 4: Future Enhancements
|
||||
- [ ] Task 4.1: Relay Performance Metrics (Latency tracking)
|
||||
- [ ] Task 4.2: Smart Relay Selection (Integrate with RelayLiveness)
|
||||
- [ ] Task 4.3: Query Optimization Suggestions
|
||||
|
||||
---
|
||||
|
||||
## Original Implementation Tasks (Reference)
|
||||
|
||||
#### Task 1.1: Create Per-Relay State Tracking Types
|
||||
|
||||
|
||||
@@ -1057,59 +1057,76 @@ export default function ReqViewer({
|
||||
|
||||
// Build comprehensive tooltip content
|
||||
const tooltipContent = (
|
||||
<div className="space-y-1.5 text-xs">
|
||||
<div className="font-semibold border-b border-border pb-1 mb-1">
|
||||
<div className="space-y-3 text-xs p-1">
|
||||
<div className="font-mono font-bold border-b border-border pb-2 mb-2 break-all text-primary">
|
||||
{url}
|
||||
</div>
|
||||
<div className="space-y-1 text-muted-foreground">
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="w-20">Connection:</span>
|
||||
<span className="text-foreground">
|
||||
{connIcon.label}
|
||||
</span>
|
||||
<div className="grid grid-cols-2 gap-x-4 gap-y-2">
|
||||
<div className="space-y-0.5">
|
||||
<div className="text-[10px] uppercase font-bold text-muted-foreground tracking-tight">
|
||||
Connection
|
||||
</div>
|
||||
<div className="flex items-center gap-1.5 font-medium">
|
||||
<span className="shrink-0">{connIcon.icon}</span>
|
||||
<span>{connIcon.label}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="w-20">Auth:</span>
|
||||
<span className="text-foreground">
|
||||
{authIcon.label}
|
||||
</span>
|
||||
|
||||
<div className="space-y-0.5">
|
||||
<div className="text-[10px] uppercase font-bold text-muted-foreground tracking-tight">
|
||||
Authentication
|
||||
</div>
|
||||
<div className="flex items-center gap-1.5 font-medium">
|
||||
<span className="shrink-0">{authIcon.icon}</span>
|
||||
<span>{authIcon.label}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{reqState && (
|
||||
<>
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="w-20">Subscription:</span>
|
||||
<span className="text-foreground capitalize">
|
||||
{reqState.subscriptionState}
|
||||
</span>
|
||||
</div>
|
||||
{reqState.eventCount > 0 && (
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="w-20">Events:</span>
|
||||
<span className="text-foreground">
|
||||
{reqState.eventCount} received
|
||||
</span>
|
||||
<div className="space-y-0.5">
|
||||
<div className="text-[10px] uppercase font-bold text-muted-foreground tracking-tight">
|
||||
Subscription
|
||||
</div>
|
||||
)}
|
||||
<div className="font-medium capitalize">
|
||||
{reqState.subscriptionState}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="space-y-0.5">
|
||||
<div className="text-[10px] uppercase font-bold text-muted-foreground tracking-tight">
|
||||
Events
|
||||
</div>
|
||||
<div className="flex items-center gap-1.5 font-medium">
|
||||
<FileText className="size-3 text-muted-foreground" />
|
||||
<span>{reqState.eventCount} received</span>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
|
||||
{nip65Info && (
|
||||
<>
|
||||
{nip65Info.readers.length > 0 && (
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="w-20">Inbox:</span>
|
||||
<span className="text-foreground">
|
||||
<div className="space-y-0.5">
|
||||
<div className="text-[10px] uppercase font-bold text-muted-foreground tracking-tight">
|
||||
Inbox (Read)
|
||||
</div>
|
||||
<div className="font-medium">
|
||||
{nip65Info.readers.length} author
|
||||
{nip65Info.readers.length !== 1 ? "s" : ""}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{nip65Info.writers.length > 0 && (
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="w-20">Outbox:</span>
|
||||
<span className="text-foreground">
|
||||
<div className="space-y-0.5">
|
||||
<div className="text-[10px] uppercase font-bold text-muted-foreground tracking-tight">
|
||||
Outbox (Write)
|
||||
</div>
|
||||
<div className="font-medium">
|
||||
{nip65Info.writers.length} author
|
||||
{nip65Info.writers.length !== 1 ? "s" : ""}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
@@ -1133,9 +1150,8 @@ export default function ReqViewer({
|
||||
<div className="flex items-center gap-1.5 flex-shrink-0">
|
||||
{/* Event count badge */}
|
||||
{reqState && reqState.eventCount > 0 && (
|
||||
<div className="flex items-center gap-1 text-[10px] text-muted-foreground font-medium">
|
||||
<FileText className="size-2.5" />
|
||||
<span>{reqState.eventCount}</span>
|
||||
<div className="text-[10px] text-muted-foreground font-medium">
|
||||
[{reqState.eventCount}]
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -1146,7 +1162,8 @@ export default function ReqViewer({
|
||||
<Check className="size-3 text-green-600/70" />
|
||||
) : (
|
||||
(reqState.subscriptionState === "receiving" ||
|
||||
reqState.subscriptionState === "waiting") && (
|
||||
reqState.subscriptionState ===
|
||||
"waiting") && (
|
||||
<Loader2 className="size-3 text-muted-foreground/40 animate-spin" />
|
||||
)
|
||||
)}
|
||||
|
||||
@@ -78,7 +78,7 @@ export function getAuthIcon(relay: RelayState | undefined) {
|
||||
},
|
||||
none: {
|
||||
icon: <Shield className="size-3 text-muted-foreground/40" />,
|
||||
label: "No Authentication Required",
|
||||
label: "Not required",
|
||||
},
|
||||
};
|
||||
return iconMap[relay.authStatus] || iconMap.none;
|
||||
|
||||
Reference in New Issue
Block a user