Skip to content

feat: add reply:, ref:, link:, dtag: query keywords#202

Merged
dergigi merged 13 commits intomasterfrom
feat/tag-filter-keywords
Mar 21, 2026
Merged

feat: add reply:, ref:, link:, dtag: query keywords#202
dergigi merged 13 commits intomasterfrom
feat/tag-filter-keywords

Conversation

@dergigi
Copy link
Copy Markdown
Owner

@dergigi dergigi commented Mar 21, 2026

Add missing query keywords for tag-based filtering, enabling full spell (NIP-A7) translation.

Resolves 201

New Keywords

Keyword Tag/Filter Description Example
reply: #e Find replies to a specific event reply:note1..., reply:<hex>
ref: #a Find events referencing a replaceable event ref:naddr1..., ref:kind:pubkey:d-tag
link: #r Find events referencing a URL link:https://github.com/nostr-protocol/nips
d: #d Find replaceable events by d-tag identifier d:my-article-slug
id: ids Fetch specific events by ID id:note1..., id:<hex>

Complete REQ Filter → Ants Mapping

With these additions, every standard REQ filter field now has an ants keyword equivalent:

REQ filter Ants keyword Status
kinds kind: / is: existing
authors by: existing
ids id: new
#t #hashtag existing
#p mentions: existing
#e reply: new
#a ref: new
#r link: new
#d d: new
search raw terms existing
since/until since:/until: existing
limit internal existing

Details

  • All keywords support multiple tokens: reply:note1... reply:note1...
  • All keywords combine with search terms and other filters
  • reply: and id: accept hex, note1..., and nevent1... bech32
  • ref: accepts raw coordinates and naddr1... bech32
  • Shared fetchDedupeAndSort() helper extracts relay selection, fallback, dedup, and sorting
  • Resolved IDs are deduped (different bech32 forms → same hex won't produce duplicates)
  • New keywords added to STRUCTURED_TOKEN_PATTERN in contentFilter
  • Search examples added for discoverability

Summary by CodeRabbit

  • New Features

    • Added structured query operators: reply:, ref:, link:, d:, and id: for targeted searching.
    • Direct id: lookups are prioritized and reply/ref/link/d filters run earlier for faster, more precise results.
    • Results are deduplicated, sorted newest-first, and fetch retries use alternative relay sets on transient failures.
    • Search examples updated to include link: and d: samples.
  • Chores

    • Test tooling directory excluded from TypeScript compilation.

dergigi added 6 commits March 21, 2026 17:52
Supports reply:<hex>, reply:<note1...>, reply:<nevent1...>
to find all events referencing a specific event via #e tags.
Multiple reply: tokens and optional search terms supported.

Part of #201
Supports ref:<kind:pubkey:d-tag> and ref:<naddr1...>
to find events referencing a specific replaceable event.
Multiple ref: tokens and optional search terms supported.

Part of #201
Supports link:<url> to find events referencing a URL via #r tags.
Multiple link: tokens and optional search terms supported.

Part of #201
- Register new strategies in searchOrchestrator.ts
- Add reply:, ref:, link: to STRUCTURED_TOKEN_PATTERN in contentFilter
- Add tag filter examples to examples.ts

Part of #201
Supports dtag:<identifier> to find replaceable events by d-tag.
Multiple dtag: tokens and optional search terms supported.

Part of #201
@vercel
Copy link
Copy Markdown
Contributor

vercel Bot commented Mar 21, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
ants Ready Ready Preview, Comment Mar 21, 2026 6:37pm

Request Review

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Mar 21, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • ✅ Review completed - (🔄 Check again to review again)
📝 Walkthrough

Walkthrough

Adds structured query token support (id:, reply:, ref:, link:, d:), five new search strategy modules and a shared fetch/dedupe utility, updates orchestrator to prioritize id then run new strategies in sequence, expands token-stripping, appends two search examples, and updates tsconfig excludes. (33 words)

Changes

Cohort / File(s) Summary
New strategy modules
src/lib/search/strategies/idSearchStrategy.ts, src/lib/search/strategies/replySearchStrategy.ts, src/lib/search/strategies/refSearchStrategy.ts, src/lib/search/strategies/linkSearchStrategy.ts, src/lib/search/strategies/dTagSearchStrategy.ts
Added five exported handlers (tryHandleIdSearch, tryHandleReplySearch, tryHandleRefSearch, tryHandleLinkSearch, tryHandleDTagSearch) that detect operator:\S+ tokens, resolve/normalize identifiers (hex, bech32, naddr/nip19, etc.), build NDKFilter (apply date/kind constraints, enforce min limits), and call shared fetch/dedupe/sort logic.
Shared strategy utilities
src/lib/search/strategies/strategyUtils.ts
Added fetchDedupeAndSort to select relay set (chosen vs broad), call subscribeAndCollect with one retry fallback, deduplicate by id, sort newest-first, and truncate to limit.
Search pipeline & token extraction
src/lib/search/searchOrchestrator.ts, src/lib/search/contentFilter.ts
runSearchStrategies now awaits tryHandleIdSearch early, then sequentially awaits tryHandleReplySearch, tryHandleRefSearch, tryHandleLinkSearch, and tryHandleDTagSearch, returning the first non-null result; STRUCTURED_TOKEN_PATTERN expanded to include reply, ref, link, id, and d.
Examples & config
src/lib/examples.ts, tsconfig.json
Appended two searchExamples entries under tag filters (link:https://github.com/nostr-protocol/nips, d:ants is:article) and added e2e and playwright.config.ts to the TypeScript exclude list.

Sequence Diagram

sequenceDiagram
    participant Client as Search Query
    participant Orchestrator as runSearchStrategies
    participant Strategy as tryHandle*Search
    participant Relays as NDK / Relay Sets
    participant Results as fetchDedupeAndSort

    Client->>Orchestrator: cleanedQuery + context
    Orchestrator->>Strategy: tryHandleIdSearch(cleanedQuery, context)
    alt id result found
        Strategy-->>Orchestrator: NDKEvent[] (return)
    else id not found
        Orchestrator->>Strategy: tryHandleReplySearch(...)
        alt reply matched
            Strategy->>Relays: subscribeAndCollect(filter, timeout)
            Relays-->>Strategy: events
            Strategy->>Results: dedupe, sort, slice
            Results-->>Orchestrator: NDKEvent[]
        else reply not matched
            Orchestrator->>Strategy: tryHandleRefSearch(...)
            alt ref matched
                Strategy->>Relays: subscribeAndCollect(filter, timeout)
                Relays-->>Strategy: events
                Strategy->>Results: dedupe, sort, slice
                Results-->>Orchestrator: NDKEvent[]
            else
                Orchestrator->>Strategy: tryHandleLinkSearch(...) -> tryHandleDTagSearch(...)
            end
        end
    end
    Orchestrator-->>Client: first non-null NDKEvent[] or empty array
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related issues

Possibly related PRs

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title accurately describes the main change: adding five new query keywords (reply:, ref:, link:, d:, id:) for tag-based filtering, which aligns with the core additions across multiple strategy files and search configuration.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/tag-filter-keywords

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (2)
src/lib/search/strategies/linkSearchStrategy.ts (1)

43-56: Consider extracting shared fetch/dedup/sort logic.

All four new strategies (reply, ref, link, dtag) share nearly identical code for relay selection, fetching with fallback, Map-based deduplication, sorting, and slicing. This could be extracted into a shared helper to reduce duplication (~15 lines per strategy).

This is a low-priority refactor since each strategy is self-contained and the current approach is clear.

♻️ Example shared helper
// In a shared module like searchUtils.ts
export async function fetchAndDedupeEvents(
  filter: NDKFilter,
  chosenRelaySet: NDKRelaySet,
  hasSearchTerm: boolean,
  abortSignal?: AbortSignal,
  limit: number
): Promise<NDKEvent[]> {
  const relaySet = hasSearchTerm ? chosenRelaySet : await getBroadRelaySet();
  
  let results: NDKEvent[];
  try {
    results = await subscribeAndCollect(filter, 10000, relaySet, abortSignal);
  } catch {
    results = await subscribeAndCollect(filter, 10000, chosenRelaySet, abortSignal);
  }
  
  const dedupe = new Map<string, NDKEvent>();
  for (const e of results) if (!dedupe.has(e.id)) dedupe.set(e.id, e);
  
  return sortEventsNewestFirst(Array.from(dedupe.values())).slice(0, limit);
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/lib/search/strategies/linkSearchStrategy.ts` around lines 43 - 56, The
repeated relay selection, fetching-with-fallback, Map deduplication, sorting and
slicing logic in the strategies should be moved into a shared helper (e.g.,
fetchAndDedupeEvents) and invoked from reply/ref/link/dtag to remove
duplication; implement a function that accepts (filter: NDKFilter,
chosenRelaySet: NDKRelaySet, hasSearchTerm: boolean, abortSignal?: AbortSignal,
limit: number), uses getBroadRelaySet when !hasSearchTerm, calls
subscribeAndCollect with the two-tier fallback, builds a Map by event.id to
dedupe, applies sortEventsNewestFirst, and returns .slice(0, limit), then
replace the inline code in each strategy with a call to this helper.
src/lib/examples.ts (1)

156-158: Consider adding examples for ref: and dtag: keywords.

The PR adds four new keywords, but only reply: and link: have examples here. Adding examples for ref: (e.g., ref:30023:pubkey:article-slug) and dtag: would improve discoverability of all the new features.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/lib/examples.ts` around lines 156 - 158, Update the tag filter examples
array in src/lib/examples.ts to include sample entries for the new keywords: add
a 'ref:' example (e.g., "ref:30023:pubkey:article-slug" to show
chainId:eventType:pubkey:slug format) and a 'dtag:' example (e.g., "dtag:alice"
or "dtag:alice:domain" to show tagged-identifier usage) alongside the existing
'reply:' and 'link:' items so the examples cover all four newly added keywords.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@src/lib/examples.ts`:
- Around line 156-158: Update the tag filter examples array in
src/lib/examples.ts to include sample entries for the new keywords: add a 'ref:'
example (e.g., "ref:30023:pubkey:article-slug" to show
chainId:eventType:pubkey:slug format) and a 'dtag:' example (e.g., "dtag:alice"
or "dtag:alice:domain" to show tagged-identifier usage) alongside the existing
'reply:' and 'link:' items so the examples cover all four newly added keywords.

In `@src/lib/search/strategies/linkSearchStrategy.ts`:
- Around line 43-56: The repeated relay selection, fetching-with-fallback, Map
deduplication, sorting and slicing logic in the strategies should be moved into
a shared helper (e.g., fetchAndDedupeEvents) and invoked from
reply/ref/link/dtag to remove duplication; implement a function that accepts
(filter: NDKFilter, chosenRelaySet: NDKRelaySet, hasSearchTerm: boolean,
abortSignal?: AbortSignal, limit: number), uses getBroadRelaySet when
!hasSearchTerm, calls subscribeAndCollect with the two-tier fallback, builds a
Map by event.id to dedupe, applies sortEventsNewestFirst, and returns .slice(0,
limit), then replace the inline code in each strategy with a call to this
helper.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 9dbcad1f-d4d5-4a53-b56b-7f3539ad5528

📥 Commits

Reviewing files that changed from the base of the PR and between 65a94fd and 133277c.

📒 Files selected for processing (8)
  • src/lib/examples.ts
  • src/lib/search/contentFilter.ts
  • src/lib/search/searchOrchestrator.ts
  • src/lib/search/strategies/dTagSearchStrategy.ts
  • src/lib/search/strategies/linkSearchStrategy.ts
  • src/lib/search/strategies/refSearchStrategy.ts
  • src/lib/search/strategies/replySearchStrategy.ts
  • tsconfig.json

dergigi added 2 commits March 21, 2026 18:34
Supports id:<hex>, id:<note1...>, id:<nevent1...>
to fetch specific events by ID. Multiple id: tokens supported.
Placed first in orchestrator since it's a direct lookup.

Part of #201
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (2)
src/lib/search/searchOrchestrator.ts (1)

20-21: Align function contract comment with actual return behavior.

The doc says “first non-null/non-empty result”, but guards return on any non-null value (including []). Update wording to avoid ambiguity.

Proposed doc wording
- * Run search strategies in order and return the first non-null/non-empty result
+ * Run search strategies in order and return the first non-null result
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/lib/search/searchOrchestrator.ts` around lines 20 - 21, The function
comment for searchOrchestrator is misleading: the implementation returns on the
first non-null value (it does not treat empty arrays as empty results), so
update the docstring for the searchOrchestrator function to state that it
returns the first non-null result (including empty arrays) and returns null only
if every strategy returns null; mention "non-null" explicitly rather than
"non-null/non-empty" to match the behavior of the existing guards.
src/lib/search/strategies/idSearchStrategy.ts (1)

40-41: Deduplicate resolved event IDs, not just raw tokens.

Different token forms (hex, note1..., nevent1...) can resolve to the same id and produce duplicate ids entries in the relay filter.

Proposed dedupe/type-safety cleanup
-  const eventIds = tokens.map(resolveEventId).filter(Boolean) as string[];
+  const eventIds = Array.from(
+    new Set(tokens.map(resolveEventId).filter((id): id is string => Boolean(id)))
+  );

As per coding guidelines, src/lib/search/**: “Performance: avoid O(n²), prefer Set-based dedup, parallel fetches”.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/lib/search/strategies/idSearchStrategy.ts` around lines 40 - 41, The
current code deduplicates raw tokens (tokens) before calling resolveEventId,
which can yield duplicate event IDs when different token forms map to the same
id; change the flow in idSearchStrategy.ts to first map idMatches through
resolveEventId (use idMatches.map(m => resolveEventId(m[1]))), filter out falsy
results, then use a Set to deduplicate the resolved event IDs and convert back
to an array (eventIds) so the relay filter contains unique IDs; ensure the
resulting eventIds is typed as string[] and keep the operations Set-based to
avoid O(n²).
🤖 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/search/strategies/idSearchStrategy.ts`:
- Around line 45-52: The id lookup currently assumes getBroadRelaySet() succeeds
and returns [] on subscribeAndCollect failures without attempting the same
lookup against the chosen relays; update idSearchStrategy so it first wraps
getBroadRelaySet() in try/catch and falls back to chosenRelaySet when
getBroadRelaySet() throws, and if subscribeAndCollect(filter, 10000, relaySet,
abortSignal) fails (catch block) retry subscribeAndCollect once more using
chosenRelaySet before settling on an empty results array; reference
getBroadRelaySet, subscribeAndCollect, chosenRelaySet and the results variable
in your changes to locate and implement the fallback logic.

---

Nitpick comments:
In `@src/lib/search/searchOrchestrator.ts`:
- Around line 20-21: The function comment for searchOrchestrator is misleading:
the implementation returns on the first non-null value (it does not treat empty
arrays as empty results), so update the docstring for the searchOrchestrator
function to state that it returns the first non-null result (including empty
arrays) and returns null only if every strategy returns null; mention "non-null"
explicitly rather than "non-null/non-empty" to match the behavior of the
existing guards.

In `@src/lib/search/strategies/idSearchStrategy.ts`:
- Around line 40-41: The current code deduplicates raw tokens (tokens) before
calling resolveEventId, which can yield duplicate event IDs when different token
forms map to the same id; change the flow in idSearchStrategy.ts to first map
idMatches through resolveEventId (use idMatches.map(m => resolveEventId(m[1]))),
filter out falsy results, then use a Set to deduplicate the resolved event IDs
and convert back to an array (eventIds) so the relay filter contains unique IDs;
ensure the resulting eventIds is typed as string[] and keep the operations
Set-based to avoid O(n²).

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 4a8bb5cc-9ae6-4a63-a468-128a2d5d2a5f

📥 Commits

Reviewing files that changed from the base of the PR and between 133277c and e58ccec.

📒 Files selected for processing (4)
  • src/lib/search/contentFilter.ts
  • src/lib/search/searchOrchestrator.ts
  • src/lib/search/strategies/dTagSearchStrategy.ts
  • src/lib/search/strategies/idSearchStrategy.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/lib/search/strategies/dTagSearchStrategy.ts
  • src/lib/search/contentFilter.ts

Comment on lines +45 to +52
const relaySet = await getBroadRelaySet();

let results: NDKEvent[];
try {
results = await subscribeAndCollect(filter, 10000, relaySet, abortSignal);
} catch {
results = [];
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Add relay fallback for id lookups when broad relay resolution/fetch fails.

id: search currently returns [] on fetch error and can throw if getBroadRelaySet() fails, so it does not degrade like other tag strategies that fallback to chosenRelaySet.

Proposed resilience fix
 export async function tryHandleIdSearch(
   cleanedQuery: string,
   context: SearchContext
 ): Promise<NDKEvent[] | null> {
-  const { abortSignal, limit } = context;
+  const { abortSignal, limit, chosenRelaySet } = context;
@@
-  const relaySet = await getBroadRelaySet();
+  let relaySet = chosenRelaySet;
+  try {
+    relaySet = await getBroadRelaySet();
+  } catch {
+    relaySet = chosenRelaySet;
+  }
@@
   let results: NDKEvent[];
   try {
     results = await subscribeAndCollect(filter, 10000, relaySet, abortSignal);
   } catch {
-    results = [];
+    if (relaySet !== chosenRelaySet) {
+      try {
+        results = await subscribeAndCollect(filter, 10000, chosenRelaySet, abortSignal);
+      } catch {
+        results = [];
+      }
+    } else {
+      results = [];
+    }
   }

As per coding guidelines, src/lib/search/**: “Graceful degradation when relays are unreachable”.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/lib/search/strategies/idSearchStrategy.ts` around lines 45 - 52, The id
lookup currently assumes getBroadRelaySet() succeeds and returns [] on
subscribeAndCollect failures without attempting the same lookup against the
chosen relays; update idSearchStrategy so it first wraps getBroadRelaySet() in
try/catch and falls back to chosenRelaySet when getBroadRelaySet() throws, and
if subscribeAndCollect(filter, 10000, relaySet, abortSignal) fails (catch block)
retry subscribeAndCollect once more using chosenRelaySet before settling on an
empty results array; reference getBroadRelaySet, subscribeAndCollect,
chosenRelaySet and the results variable in your changes to locate and implement
the fallback logic.

@dergigi dergigi self-assigned this Mar 21, 2026
- Extract shared fetch/dedup/sort logic into strategyUtils.ts
- Dedupe resolved event IDs (not just raw tokens) in id strategy
- Add fallback relay handling in shared helper
- Fix orchestrator docstring (non-null, not non-null/non-empty)
- Add d: example to examples.ts
- Rename dtag: to d: in all strategies
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
src/lib/search/strategies/refSearchStrategy.ts (1)

11-21: Verify NIP-33 coordinate regex handles edge cases.

The regex /^\d+:[0-9a-f]{64}:/ correctly validates the basic structure, but note:

  • It requires lowercase hex only ([0-9a-f]), while input could contain uppercase hex
  • This means 30023:ABCD... would fail validation and fall through to naddr decoding

If uppercase hex coordinates are valid input (they technically are valid hex), consider case-insensitive matching:

💡 Optional fix for case-insensitive hex
 function resolveATagCoordinate(token: string): string | null {
-  if (/^\d+:[0-9a-f]{64}:/.test(token)) return token;
+  if (/^\d+:[0-9a-fA-F]{64}:/i.test(token)) return token.toLowerCase();
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/lib/search/strategies/refSearchStrategy.ts` around lines 11 - 21, The
regex in resolveATagCoordinate only accepts lowercase hex and will reject valid
coordinates with uppercase letters; update the validation to be case-insensitive
(e.g., add the regex "i" flag or expand to [0-9a-fA-F]) so tokens like
"30023:ABCD...:" are accepted, while keeping the existing nip19.decode fallback
and return behavior unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@src/lib/search/strategies/refSearchStrategy.ts`:
- Around line 11-21: The regex in resolveATagCoordinate only accepts lowercase
hex and will reject valid coordinates with uppercase letters; update the
validation to be case-insensitive (e.g., add the regex "i" flag or expand to
[0-9a-fA-F]) so tokens like "30023:ABCD...:" are accepted, while keeping the
existing nip19.decode fallback and return behavior unchanged.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 7c89d80f-29ef-43b5-89f5-12ae18842f0c

📥 Commits

Reviewing files that changed from the base of the PR and between e58ccec and 058e2e5.

📒 Files selected for processing (8)
  • src/lib/examples.ts
  • src/lib/search/searchOrchestrator.ts
  • src/lib/search/strategies/dTagSearchStrategy.ts
  • src/lib/search/strategies/idSearchStrategy.ts
  • src/lib/search/strategies/linkSearchStrategy.ts
  • src/lib/search/strategies/refSearchStrategy.ts
  • src/lib/search/strategies/replySearchStrategy.ts
  • src/lib/search/strategies/strategyUtils.ts
🚧 Files skipped from review as they are similar to previous changes (4)
  • src/lib/search/searchOrchestrator.ts
  • src/lib/search/strategies/replySearchStrategy.ts
  • src/lib/search/strategies/linkSearchStrategy.ts
  • src/lib/search/strategies/idSearchStrategy.ts

@dergigi
Copy link
Copy Markdown
Owner Author

dergigi commented Mar 21, 2026

What are the Top5 things we could improve in regards to the things we implemented on this branch?

@dergigi
Copy link
Copy Markdown
Owner Author

dergigi commented Mar 21, 2026

Top 5 improvements for the keywords on this branch:

1. d: is too greedy — collides with other tokens
The regex \bd:(\S+) will match any d:something in the query. This could collide with future keywords or confuse users who type something like d:5 expecting a date. We should consider requiring d: to only match when there's no other strategy that claims the token, or require the value to look like a valid d-tag identifier (non-numeric, non-date-like).

2. link: should normalize URLs before querying
Events tag URLs inconsistently — some with trailing slashes, some without, some with www., some without. link:https://example.com/ won't match events tagged with https://example.com. We could normalize both the query URL and do a secondary lookup with/without trailing slash.

3. Combined keyword queries don't work yet
reply:note1... by:dergigi should find replies to a note by dergigi, but right now reply: claims the whole query and the by: in the residual only works if it hits the NIP-50 search field. We'd need to strip by: from the residual and add it as an authors filter on the same subscription — same pattern mentionsSearchStrategy already does.

4. id: should bypass the orchestrator entirely
Direct ID lookups are fundamentally different from searches — they should be instant and don't need NIP-50 relays, date filters, or kind filters. Right now id: goes through the normal strategy pipeline. Moving it before the orchestrator (alongside the existing nevent/naddr bech32 handling) would be cleaner and faster.

5. No /help or /kinds-style documentation for new keywords
Users have no way to discover these keywords exist other than stumbling on examples. A /keywords command or adding them to /help output would make them findable. Even just updating the README's query syntax section would help.

@dergigi
Copy link
Copy Markdown
Owner Author

dergigi commented Mar 21, 2026

I think (3) and (4) are the most important. We should implement those now.

dergigi added 2 commits March 21, 2026 19:33
All tag strategies (reply:, ref:, link:, d:) now parse by: tokens
from the residual text and add them as an authors filter on the
subscription. This means queries like 'reply:note1... by:dergigi'
correctly find replies by that author.

Extracted shared parseResidual() helper in strategyUtils.ts.
id: queries now bypass the full search pipeline (relay discovery,
kind/date parsing, NIP-50 extensions). handleIdLookup() is a
standalone function that only needs the raw query, abort signal,
and limit — no SearchContext required.

Also: all tag strategies now support by: combinations via shared
parseResidual() helper.
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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/lib/search/strategies/idSearchStrategy.ts`:
- Around line 37-44: The call to getBroadRelaySet() in handleIdLookup can throw
and must be guarded; wrap getBroadRelaySet() in a try/catch and on error set
relaySet to a safe fallback (e.g., an empty array or a lightweight fallback
relay set) so the subsequent subscribeAndCollect(filter, 10000, relaySet,
abortSignal) call gracefully degrades instead of throwing; ensure the catch
preserves abortSignal behavior and logs or annotates the fallback so callers can
distinguish a degraded result.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: fb3cadf4-edff-430b-8b76-04ba93384fe1

📥 Commits

Reviewing files that changed from the base of the PR and between da752b5 and 4276210.

📒 Files selected for processing (9)
  • src/lib/examples.ts
  • src/lib/search.ts
  • src/lib/search/searchOrchestrator.ts
  • src/lib/search/strategies/dTagSearchStrategy.ts
  • src/lib/search/strategies/idSearchStrategy.ts
  • src/lib/search/strategies/linkSearchStrategy.ts
  • src/lib/search/strategies/refSearchStrategy.ts
  • src/lib/search/strategies/replySearchStrategy.ts
  • src/lib/search/strategies/strategyUtils.ts
🚧 Files skipped from review as they are similar to previous changes (5)
  • src/lib/examples.ts
  • src/lib/search/strategies/linkSearchStrategy.ts
  • src/lib/search/strategies/dTagSearchStrategy.ts
  • src/lib/search/strategies/replySearchStrategy.ts
  • src/lib/search/strategies/refSearchStrategy.ts

Comment on lines +37 to +44
const relaySet = await getBroadRelaySet();

let results: NDKEvent[];
try {
results = await subscribeAndCollect(filter, 10000, relaySet, abortSignal);
} catch {
results = [];
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Wrap getBroadRelaySet() in try/catch for graceful degradation.

If getBroadRelaySet() throws (e.g., all relays are dead), the entire id: lookup will fail with an unhandled exception. Unlike fetchDedupeAndSort in strategyUtils.ts which handles this case, handleIdLookup lacks fallback protection.

🛡️ Proposed fix to add relay fallback
+import { NDKRelaySet } from '@nostr-dev-kit/ndk';
+import { ndk } from '../../ndk';
+import { RELAYS } from '../../constants';
+
 export async function handleIdLookup(
   query: string,
   abortSignal?: AbortSignal,
   limit: number = 200
 ): Promise<NDKEvent[] | null> {
   const matches = Array.from(query.matchAll(/\bid:(\S+)/gi));
   if (matches.length === 0) return null;
 
   const eventIds = Array.from(
     new Set(matches.map((m) => resolveEventId(m[1] || '')).filter((id): id is string => Boolean(id)))
   );
   if (eventIds.length === 0) return [];
 
   const filter: NDKFilter = { ids: eventIds };
-  const relaySet = await getBroadRelaySet();
+
+  let relaySet: NDKRelaySet;
+  try {
+    relaySet = await getBroadRelaySet();
+  } catch {
+    // Fallback to default relays if broad relay set fails
+    relaySet = NDKRelaySet.fromRelayUrls(RELAYS.DEFAULT, ndk);
+  }
 
   let results: NDKEvent[];
   try {
     results = await subscribeAndCollect(filter, 10000, relaySet, abortSignal);
   } catch {
     results = [];
   }

As per coding guidelines, src/lib/search/**: "Graceful degradation when relays are unreachable".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/lib/search/strategies/idSearchStrategy.ts` around lines 37 - 44, The call
to getBroadRelaySet() in handleIdLookup can throw and must be guarded; wrap
getBroadRelaySet() in a try/catch and on error set relaySet to a safe fallback
(e.g., an empty array or a lightweight fallback relay set) so the subsequent
subscribeAndCollect(filter, 10000, relaySet, abortSignal) call gracefully
degrades instead of throwing; ensure the catch preserves abortSignal behavior
and logs or annotates the fallback so callers can distinguish a degraded result.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat: add missing query keywords for full spell translation

1 participant