Conversation
📝 WalkthroughWalkthroughReplaces pool-based fetching with RxNostr streams and request factories; refactors query core to use Changes
Sequence Diagram(s)sequenceDiagram
participant Browser as Client/Browser
participant App as App Bootstrap
participant RxNostr as RxNostr (rxForwardReq / rxBackwardReq)
participant Relays as Relay Network
participant Cache as TanStack Query Cache
participant Routes as Route Loaders
Note over App,RxNostr: App initializes rxNostr and subscriptions
App->>RxNostr: start forward req (kinds 1,7 since 5m)
RxNostr->>Relays: subscribe/request events (kinds 0,1,3,7)
Relays-->>RxNostr: stream events
RxNostr->>App: emit events
App->>Cache: setQueryData per-kind (metadata, text, followlist, reaction)
Routes->>RxNostr: emit backward request filters via rxBackwardReq
RxNostr->>Relays: send filtered requests
Relays-->>RxNostr: return matching events
RxNostr->>Cache: update cache with results
Cache-->>Routes: serve cached query data
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Possibly related PRs
Suggested labels
🚥 Pre-merge checks | ✅ 1 | ❌ 2❌ Failed checks (1 warning, 1 inconclusive)
✅ Passed checks (1 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Deploying with
|
| Status | Name | Latest Commit | Preview URL | Updated (UTC) |
|---|---|---|---|---|
| ✅ Deployment successful! View logs |
astraea | 2ddc291 | Commit Preview URL Branch Preview URL |
Mar 30 2026, 01:13 PM |
There was a problem hiding this comment.
Actionable comments posted: 3
🧹 Nitpick comments (1)
src/routes/(app)/home.tsx (1)
7-7: Minor: Extra space in function signature.There's a double space between
asyncandloader.✨ Suggested fix
- async loader({ context: { queryClient, rxBackwardReq, pubkey } }) { + async loader({ context: { queryClient, rxBackwardReq, pubkey } }) {🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/routes/`(app)/home.tsx at line 7, Remove the accidental double space in the function declaration so the async keyword is directly followed by the function name; edit the signature "async loader({ context: { queryClient, rxBackwardReq, pubkey } })" to "async loader(...)" (i.e., collapse the two spaces to one) to match standard formatting and avoid linter/style warnings for the loader function.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@src/lib/nostr/query-helpers.ts`:
- Around line 67-85: The early-abort check inside queryFn currently calls reject
when signal.aborted but does not return, so execution continues to emit the
filter and subscribe; update queryFn to immediately return after rejecting when
signal.aborted (i.e., after reject(signal.reason) do a return), so the
subsequent rxBackwardReq.emit(filter, options), new QueryObserver(queryClient, {
queryKey }) and subscription setup are skipped; keep the existing abort event
listener/unsubscribe logic intact.
- Around line 68-85: The Promise can hang if no event arrives because you only
resolve on next; modify the subscription created by new
QueryObserver(...).subscribe to use the full observer form ({ next, error,
complete }) or add listeners to rxBackwardReq's completion so that on complete
you unsubscribe, remove the abort listener, and reject(new Error('NotFound' or
'Query completed without result')), and on error reject with that error; also
ensure abort handler cleans up the complete/error handlers and that you remove
the signal event listener after resolution/rejection. Target symbols:
QueryObserver, observer.subscribe, unsubscribe, rxBackwardReq.emit, and the
signal abort handler.
In `@src/main.tsx`:
- Around line 73-76: The emit call rxForwardReq.emit({...}) is executed at
module load before relays are configured; move or defer this emission so it runs
after rxNostr.setDefaultRelays() has been called (e.g., inside the router
route's beforeLoad hook or any initialization that runs after router/context
setup). Locate the module-level rxForwardReq.emit invocation and remove or guard
it, then add an emission point in the route beforeLoad (or a post-relay-setup
initializer) so rxNostr has default relays configured before calling
rxForwardReq.emit; keep the same event payload and ensure it uses the existing
rxNostr and rxForwardReq instances.
---
Nitpick comments:
In `@src/routes/`(app)/home.tsx:
- Line 7: Remove the accidental double space in the function declaration so the
async keyword is directly followed by the function name; edit the signature
"async loader({ context: { queryClient, rxBackwardReq, pubkey } })" to "async
loader(...)" (i.e., collapse the two spaces to one) to match standard formatting
and avoid linter/style warnings for the loader function.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: ca47d6f6-5a11-4dc9-a3d0-61b74573866a
⛔ Files ignored due to path filters (1)
package-lock.jsonis excluded by!**/package-lock.json
📒 Files selected for processing (14)
eslint.config.jspackage.jsonsrc/lib/nostr/hooks/use-nostr.tssrc/lib/nostr/kinds/0.tssrc/lib/nostr/kinds/3.tssrc/lib/nostr/query-helpers.tssrc/lib/nostr/rx-nostr.tssrc/lib/query-key.tssrc/main.tsxsrc/routes/(app)/(profile)/npub1{$id}.tsxsrc/routes/(app)/(textnote)/nevent1{$id}.tsxsrc/routes/(app)/home.tsxsrc/routes/(app)/route.tsxsrc/routes/(app)/settings/relays.tsx
src/lib/nostr/query-helpers.ts
Outdated
| return new Promise<z.infer<Schema>>((resolve, reject) => { | ||
| if (signal.aborted) { | ||
| reject(signal.reason) | ||
| } | ||
| const options = relays ? { relays } : undefined | ||
| rxBackwardReq.emit(filter, options) | ||
| const observer = new QueryObserver(queryClient, { queryKey }) | ||
| const unsubscribe = observer.subscribe((result) => { | ||
| if (result.data !== undefined) { | ||
| resolve(result.data as z.infer<Schema>) | ||
| } | ||
| }) | ||
|
|
||
| signal.addEventListener('abort', () => { | ||
| unsubscribe() | ||
| reject(new Error('Query aborted')) | ||
| }, { signal }) | ||
| }) |
There was a problem hiding this comment.
🧩 Analysis chain
🌐 Web query:
How does rx-nostr RxBackwardReq signal end of stored events (EOSE)?
💡 Result:
In rx-nostr, RxBackwardReq (created via createRxBackwardReq) is designed for the "Backward Strategy," which collects past events from relays. It signals the end of stored events (EOSE) by completing the RxJS Observable subscription when an EOSE message is received from the relay for the corresponding subscription ID. This is shown in usage examples where developers subscribe to rxNostr.use(rxBackwardReq.pipe(...)) and use the complete callback to resolve promises or handle the end of historical event delivery, as rx-nostr automatically manages sending CLOSE after EOSE and handles the protocol details transparently per NIP-01.
Citations:
- 1: https://github.com/eyemono-moe/streets/blob/main/src/context/rxNostr.tsx
- 2: https://trap.jp/post/2414/
- 3: https://github.com/penpenpng/rx-nostr
🏁 Script executed:
# First, let's look at the full function context around lines 68-85
cat -n src/lib/nostr/query-helpers.ts | head -100 | tail -50Repository: mouse484/astraea
Length of output: 1898
🏁 Script executed:
# Check the full file size to understand scope
wc -l src/lib/nostr/query-helpers.tsRepository: mouse484/astraea
Length of output: 93
🏁 Script executed:
# Look at the imports and setup
head -50 src/lib/nostr/query-helpers.tsRepository: mouse484/astraea
Length of output: 1378
🏁 Script executed:
# Search for where rxBackwardReq is used and subscribed to
rg "rxBackwardReq" -A 3 -B 3 --type ts --type tsxRepository: mouse484/astraea
Length of output: 87
🏁 Script executed:
# Look for EOSE handling in the codebase
rg "EOSE|eose" --type ts --type tsxRepository: mouse484/astraea
Length of output: 87
🏁 Script executed:
# Search for where the query cache gets populated from rxBackwardReq
rg "queryClient.*setQueryData|mutate|setInfiniteQueryData" src/lib/nostr --type tsRepository: mouse484/astraea
Length of output: 42
🏁 Script executed:
# Search for where rxBackwardReq is used and subscribed to
rg "rxBackwardReq" -A 3 -B 3Repository: mouse484/astraea
Length of output: 6853
🏁 Script executed:
# Look for EOSE handling in the codebase
rg "EOSE|eose"Repository: mouse484/astraea
Length of output: 42
🏁 Script executed:
# Search for where the query cache gets populated from rxBackwardReq
rg "queryClient.*setQueryData|mutate|setInfiniteQueryData" src/lib/nostrRepository: mouse484/astraea
Length of output: 42
🏁 Script executed:
# Look at the full subscription in main.tsx
cat -n src/main.tsx | grep -A 20 "rxNostr.use(rxBackwardReq"Repository: mouse484/astraea
Length of output: 930
🏁 Script executed:
# Search for setQueryData calls
rg "setQueryData"Repository: mouse484/astraea
Length of output: 643
🏁 Script executed:
# Find the use-nostr-events hook
find . -name "*use-nostr*" -o -name "*nostr-events*" | head -20Repository: mouse484/astraea
Length of output: 136
🏁 Script executed:
# Look at use-nostr-events hook mentioned in learnings
cat -n src/lib/nostr/hooks/use-nostr-events.tsRepository: mouse484/astraea
Length of output: 2394
🏁 Script executed:
# Check if there's any EOSE or complete handler registered anywhere
rg "complete|finalize|EOSE"Repository: mouse484/astraea
Length of output: 42
🏁 Script executed:
# Look for error handling in the rxBackwardReq subscription
cat -n src/main.tsx | grep -A 30 "subscribe"Repository: mouse484/astraea
Length of output: 1318
Query will hang indefinitely if the requested event doesn't exist.
The QueryObserver only resolves when result.data !== undefined. While the root subscription in src/main.tsx handles incoming events, there's no error or complete handler for when rxBackwardReq completes (after EOSE is received from relays). If no matching event arrives before EOSE, the Promise never resolves or rejects. Consider adding a complete/error handler to the rxBackwardReq subscription or implementing a timeout to reject with a "not found" error.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/lib/nostr/query-helpers.ts` around lines 68 - 85, The Promise can hang if
no event arrives because you only resolve on next; modify the subscription
created by new QueryObserver(...).subscribe to use the full observer form ({
next, error, complete }) or add listeners to rxBackwardReq's completion so that
on complete you unsubscribe, remove the abort listener, and reject(new
Error('NotFound' or 'Query completed without result')), and on error reject with
that error; also ensure abort handler cleans up the complete/error handlers and
that you remove the signal event listener after resolution/rejection. Target
symbols: QueryObserver, observer.subscribe, unsubscribe, rxBackwardReq.emit, and
the signal abort handler.
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (1)
src/main.tsx (1)
32-66: Consider adding error handling to the subscription.The merged subscription has no error handler. If the rx-nostr stream errors, it will silently fail. Consider adding an error callback for observability.
♻️ Suggested improvement
merge( rxNostr.use(rxForwardReq), rxNostr.use(rxBackwardReq.pipe(bufferTime(1000), batch())), ) - .subscribe(({ event }) => { + .subscribe({ + next: ({ event }) => { // TODO: kindによってのsetQueryDataの振り分けロジックを検討し改善する switch (event.kind) { // ... existing cases } - }) + }, + error: (err) => console.error('RxNostr subscription error:', err), + })🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/main.tsx` around lines 32 - 66, The merged observable created from merge(rxNostr.use(rxForwardReq), rxNostr.use(rxBackwardReq.pipe(bufferTime(1000), batch()))) is subscribed with only a next handler so stream errors will be swallowed; update the subscribe call on that merged stream to include an error callback (or use subscribe({ next, error, complete })) that logs the error (e.g., via console.error or your app logger) and performs any cleanup/recovery (like invalidating or refetching via queryClient if needed); modify the subscribe invocation that currently calls setMetadataQuery, setTextNoteQuery, setFollowListQuery, and setReactionQuery to provide this error handler.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@src/lib/nostr/kinds/10002.ts`:
- Around line 15-19: The createQuery call for relayListQuery is missing the
required schema parameter; update the createQuery<RelayListEvent> invocation to
include schema: RelayListEventSchema so that setQueryFunction can call
schema.safeParse(event) safely; modify the relayListQuery object (the
createQuery(...) call) to pass RelayListEventSchema along with name, kind, and
filterKey.
---
Nitpick comments:
In `@src/main.tsx`:
- Around line 32-66: The merged observable created from
merge(rxNostr.use(rxForwardReq),
rxNostr.use(rxBackwardReq.pipe(bufferTime(1000), batch()))) is subscribed with
only a next handler so stream errors will be swallowed; update the subscribe
call on that merged stream to include an error callback (or use subscribe({
next, error, complete })) that logs the error (e.g., via console.error or your
app logger) and performs any cleanup/recovery (like invalidating or refetching
via queryClient if needed); modify the subscribe invocation that currently calls
setMetadataQuery, setTextNoteQuery, setFollowListQuery, and setReactionQuery to
provide this error handler.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 34bc6b84-579f-4d00-86f7-edcce0a56f6e
⛔ Files ignored due to path filters (1)
package-lock.jsonis excluded by!**/package-lock.json
📒 Files selected for processing (14)
package.jsonsrc/components/text-note/TextNote.tsxsrc/components/text-note/footer/Zap.tsxsrc/lib/nostr/hooks/use-nostr-events.tssrc/lib/nostr/hooks/use-nostr.tssrc/lib/nostr/kinds/0.tssrc/lib/nostr/kinds/1.tssrc/lib/nostr/kinds/10002.tssrc/lib/nostr/kinds/3.tssrc/lib/nostr/kinds/7.tssrc/lib/nostr/query-helpers.tssrc/lib/query-key.tssrc/main.tsxsrc/routes/(app)/(profile)/npub1{$id}.tsx
✅ Files skipped from review due to trivial changes (3)
- src/lib/nostr/hooks/use-nostr-events.ts
- src/components/text-note/footer/Zap.tsx
- package.json
🚧 Files skipped from review as they are similar to previous changes (5)
- src/routes/(app)/(profile)/npub1{$id}.tsx
- src/lib/nostr/hooks/use-nostr.ts
- src/lib/nostr/kinds/0.ts
- src/lib/query-key.ts
- src/lib/nostr/kinds/3.ts
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@src/components/NostrEvent.tsx`:
- Around line 8-11: The NostrEvent component currently cannot forward relay
hints because queryOptions only carries [id, setQueryKeyFunction] and useNostr()
has no relay context; update the shape of queryOptions to optionally include
relays (e.g. queryOptions: readonly [string, Parameters<QueryFunction<T>>[2], {
relays?: string[] }?]) or add a context-shaping callback prop on NostrEvent that
returns { relays } from the parsed nevent; then update NostrEvent to accept and
pass that relays object into useNostr()/the query context and update callers
(TextNoteContent.tsx) to supply the parsed nevent.decoded.relays into NostrEvent
so the existing query helper (queryFunction in query-helpers.ts which reads
context.relays) can use the hinted relays.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: b4d76142-85c0-4eb5-a3f1-a341d4e67e4c
📒 Files selected for processing (5)
src/components/NostrEvent.tsxsrc/components/text-note/TextNote.tsxsrc/components/text-note/TextNoteContent.tsxsrc/lib/nostr/query-helpers.tssrc/lib/query-key.ts
🚧 Files skipped from review as they are similar to previous changes (2)
- src/lib/query-key.ts
- src/lib/nostr/query-helpers.ts
| queryOptions: readonly [ | ||
| string, | ||
| Parameters<QueryFunction<T>>[2], | ||
| ] |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== parseTextNoteContent / nevent payload =="
fd -i 'text-note-content.ts' src -x sed -n '1,220p' {}
echo
echo "== query helper relay plumbing =="
fd -i 'query-helpers.ts' src -x sed -n '1,200p' {}
echo
echo "== decoded nevent relay usage =="
rg -n -C2 --type=ts --type=tsx 'decoded\.(relay|relays)|\bnevent\b' srcRepository: mouse484/astraea
Length of output: 4591
🏁 Script executed:
# First, find the createEvent implementation to understand what nevent decode returns
fd -i 'nip19' src -type fRepository: mouse484/astraea
Length of output: 230
🏁 Script executed:
# Search for NostrEvent.tsx to see how it's using decoded events
fd -i 'NostrEvent.tsx' src -type fRepository: mouse484/astraea
Length of output: 230
🏁 Script executed:
# Better search for nevent relay usage without the problematic tsx flag
rg -n 'nevent.*relay|relays.*nevent' src --type tsRepository: mouse484/astraea
Length of output: 155
🏁 Script executed:
# Look for how createEvent is being used with relays
rg -n -C3 'createEvent' src --type ts --type tsxRepository: mouse484/astraea
Length of output: 87
🏁 Script executed:
# Fix the fd command - use correct syntax
find src -name '*nip19*' -type fRepository: mouse484/astraea
Length of output: 82
🏁 Script executed:
# Find and examine NostrEvent.tsx
find src -name 'NostrEvent.tsx' -type fRepository: mouse484/astraea
Length of output: 89
🏁 Script executed:
# Look at the nevent route that uses decoded.relays
cat src/routes/\(app\)/\(textnote\)/nevent1\{\$id\}.tsxRepository: mouse484/astraea
Length of output: 1706
🏁 Script executed:
# Examine NostrEvent.tsx, especially the lines mentioned in the review (8-11 and 15-23)
cat -n src/components/NostrEvent.tsx | head -40Repository: mouse484/astraea
Length of output: 1112
🏁 Script executed:
# Look at the createEvent implementation in nip19.ts
cat -n src/lib/nostr/nip19.tsRepository: mouse484/astraea
Length of output: 2673
🏁 Script executed:
# Search for usage of NostrEvent component to see if it's being used with decoded nevents
rg -n 'NostrEvent' src --type-add 'tsx:*.tsx' -t tsxRepository: mouse484/astraea
Length of output: 1142
🏁 Script executed:
# Check if there are any inline nevent decoding with relays being used with NostrEvent
rg -n -B5 -A5 'decoded.*relays|NostrEvent.*nevent' src --type-add 'tsx:*.tsx' -t tsxRepository: mouse484/astraea
Length of output: 981
🏁 Script executed:
# Look at TextNoteContent.tsx to see how it's using NostrEvent with decoded content
cat -n src/components/text-note/TextNoteContent.tsxRepository: mouse484/astraea
Length of output: 1597
🏁 Script executed:
# Check the useNostr hook to understand what queryContext provides
find src -name 'use-nostr*' -type fRepository: mouse484/astraea
Length of output: 132
🏁 Script executed:
# Check if relays parameter in queryFunction is actually used
cat -n src/lib/nostr/query-helpers.ts | head -80Repository: mouse484/astraea
Length of output: 3194
🏁 Script executed:
# Examine useNostr hook to see what it provides
cat -n src/lib/nostr/hooks/use-nostr.tsRepository: mouse484/astraea
Length of output: 1515
🏁 Script executed:
# Check if nevent decoded data actually includes relays field
rg -n 'decoded\.relays' srcRepository: mouse484/astraea
Length of output: 155
Add mechanism to pass relay hints through NostrEvent component.
The decoded nevent payload exposes relay hints via .decoded.relays (confirmed in route handlers), and the query function properly uses them when available in context.relays (line 63-64 in query-helpers.ts). However, TextNoteContent.tsx parses nevent identifiers with relays but cannot pass them through the NostrEvent component—queryOptions only carries [id, setQueryKeyFunction], while useNostr() provides no relay context. This leaves embedded note references unable to leverage their hinted relays.
Consider either expanding queryOptions to include optional relays, or adding a context-shaping callback to preserve this path.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/components/NostrEvent.tsx` around lines 8 - 11, The NostrEvent component
currently cannot forward relay hints because queryOptions only carries [id,
setQueryKeyFunction] and useNostr() has no relay context; update the shape of
queryOptions to optionally include relays (e.g. queryOptions: readonly [string,
Parameters<QueryFunction<T>>[2], { relays?: string[] }?]) or add a
context-shaping callback prop on NostrEvent that returns { relays } from the
parsed nevent; then update NostrEvent to accept and pass that relays object into
useNostr()/the query context and update callers (TextNoteContent.tsx) to supply
the parsed nevent.decoded.relays into NostrEvent so the existing query helper
(queryFunction in query-helpers.ts which reads context.relays) can use the
hinted relays.
Summary by CodeRabbit
New Features
Refactor
Chores
Style