5a437ed5ac
- Collapsible sticky section headers with animated chevron - Full header row clickable to toggle collapse - SearchResultSorter, SearchSortOrder, pseudo-kind filtering - TextFieldValue cursor preservation, relay timeout improvements Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
9.5 KiB
9.5 KiB
Brainstorm: Advanced Search for Desktop
Date: 2026-03-10
Status: Draft
Branch: TBD (feat/desktop-advanced-search)
What We're Building
Full-featured advanced search for Amethyst Desktop with:
- Twitter-style query operators (
from:,kind:,since:, etc.) that map to NIP-50 Filter fields - Form-based UI (expandable panel below search bar) for users who don't want to learn syntax
- Bidirectional sync between text operators and form controls — editing one updates the other
- Extensible kind presets — toggle groups like Notes, Articles, Media, Communities
- Future AI bridge (follow-up) — natural language → structured query conversion
Philosophy: Relay-first, pragmatic. Desktop users have resources; we prioritize completeness over privacy. Privacy controls available but not default friction.
Why This Approach
- No standard Nostr query language exists — we define one that maps cleanly to
Filterfields - Dual interface (text + form) — power users get speed, casual users get discoverability
- Current desktop search is minimal — only kind 0 + 1, no operators, no kind filtering
- Android has 30+ kinds but no query language — desktop leapfrogs with both
- AI deferred — core query system must work standalone first; AI is a parsing layer on top
How Other Clients Do Search
| Client | Approach | Operators | Notable |
|---|---|---|---|
| Amethyst Android | Local cache + NIP-50, 30+ kinds | None (plain text) | Search relay list (kind 10007) |
| Primal | Proprietary caching server | Form UI only | Event type, time range, scope dropdowns |
| Coracle | NIP-50 + configurable relays | None | Also supports DVM requests (NIP-90) |
| Damus | NIP-50 relay search | None | User/profile focused |
| noStrudel | Local relay + NIP-50 | None | IndexedDB local indexing |
| Gossip | NIP-50 relay search | None | Desktop Rust client |
| Noogle.lol | NIP-90 DVM search | from:npub from:me |
Pay-per-query via Lightning |
Key insight: No client ships a query operator language. This is greenfield.
Proposed Query Schema
Operators map directly to Filter fields and NIP-50 extensions:
Core Operators
| Operator | Maps To | Example | Notes |
|---|---|---|---|
from:<npub|name> |
Filter.authors |
from:npub1abc... |
Resolve names via NIP-05/local cache |
kind:<number|name> |
Filter.kinds |
kind:note or kind:1 |
Named aliases for common kinds |
since:<date> |
Filter.since |
since:2025-01-01 |
ISO 8601 date parsing |
until:<date> |
Filter.until |
until:2025-06-30 |
ISO 8601 date parsing |
#<tag> |
Filter.tags["t"] |
#bitcoin |
Hashtag filter |
"exact phrase" |
Quoted in Filter.search |
"lightning network" |
Relay-dependent support |
-<term> |
Client-side exclusion | -spam |
Post-filter after relay results |
NIP-50 Extension Operators
| Operator | NIP-50 Extension | Example |
|---|---|---|
lang:<code> |
language:xx |
lang:en |
domain:<nip05> |
domain:xx |
domain:nostr.com |
nsfw:<bool> |
nsfw:xx |
nsfw:false |
Kind Name Aliases
| Alias | Kind(s) | Description |
|---|---|---|
note |
1 | Short text note |
article |
30023 | Long-form content |
repost |
6 | Reposts |
reply |
1 (with e tag) |
Replies (client-side filter) |
media |
1 (with imeta tag) |
Notes with media |
channel |
40, 41, 42 | Public channels |
live |
30311 | Live activities |
community |
34550 | Communities |
wiki |
30818 | Wiki pages |
video |
34235 | Video events |
classified |
30402 | Classifieds |
profile |
0 | Metadata/profiles |
Boolean Logic
| Syntax | Behavior |
|---|---|
bitcoin lightning |
AND (default) |
bitcoin OR lightning |
OR — requires multiple relay queries |
-spam |
NOT — client-side exclusion |
UX Design
Search Bar (Default State)
[ Search notes, people, tags... ] [Advanced v]
Expanded Advanced Panel
[ from:npub1abc kind:note since:2025-01 bitcoin ] [Advanced ^]
+------------------------------------------------------------------+
| Content Type: [x] Notes [ ] Articles [ ] Media [ ] All |
| Author: [ npub or name... ] [+ Add] |
| Date Range: [ 2025-01-01 ] to [ today ] |
| Language: [ Any v ] |
| Hashtags: [ #bitcoin ] [+ Add] |
| Exclude: [ spam, nsfw... ] [+ Add] |
| |
| [Clear Filters] [Search] |
+------------------------------------------------------------------+
Bidirectional Sync
- Typing
from:npub1abcin search bar → Author field populates in panel - Selecting "Articles" checkbox →
kind:articleappears in search bar - Editing either side updates the other in real-time
- Form is the "visual representation" of the query string
Result Display
- Results grouped by type: People, Notes, Articles, Channels
- Each result shows: content preview, author, timestamp, kind badge
- Infinite scroll with "Load more from relays" button
- Sort: Relevance (default, relay-determined) or Chronological
Architecture
Query Pipeline
User Input (text or form)
|
v
QueryParser (commons/commonMain)
|-- Tokenize operators: from:, kind:, since:, etc.
|-- Resolve names → hex pubkeys (local cache + NIP-05)
|-- Parse dates → unix timestamps
|-- Map kind aliases → kind numbers
|
v
SearchQuery (data class in commons)
|-- text: String (free text for NIP-50 search field)
|-- authors: List<HexKey>
|-- kinds: List<Int>
|-- since: Long?
|-- until: Long?
|-- tags: Map<String, List<String>>
|-- excludeTerms: List<String>
|-- language: String?
|-- nip50Extensions: Map<String, String>
|
v
FilterBuilder (desktop)
|-- Convert SearchQuery → List<Filter>
|-- Split by kind groups (like Android: 3 filters, ~10 kinds each)
|-- Inject NIP-50 extensions into search string
|
v
Relay Subscription
|-- Use search relay list (kind 10007) or connected relays
|-- Send REQ with filters
|-- Aggregate results
|
v
Client-side Post-filter
|-- Apply exclusions (-term)
|-- Apply "reply" detection (has e tag)
|-- Apply "media" detection (has imeta tag)
|
v
Results Display
Module Placement
| Component | Module | Rationale |
|---|---|---|
QueryParser |
commons/commonMain |
Reusable for Android later |
SearchQuery |
commons/commonMain |
Shared data model |
QuerySerializer |
commons/commonMain |
SearchQuery ↔ string conversion |
AdvancedSearchPanel |
desktopApp |
Desktop-specific UI |
SearchScreen (updated) |
desktopApp |
Desktop layout |
FilterBuilder (updated) |
desktopApp |
Desktop filter assembly |
| Kind alias registry | commons/commonMain |
Shared kind name mapping |
Search Relay Management
- Use existing
SearchRelayListEvent(kind 10007) from quartz - Desktop UI to configure search relays (settings page)
- Default fallback:
relay.nostr.band,nostr.wine,relay.damus.io - Future: auto-discover NIP-50 capable relays via NIP-11
Privacy Considerations
| Concern | Mitigation | Default |
|---|---|---|
| Relay sees search queries | User-configurable search relay list | On (kind 10007) |
| Relay sees IP + query | VPN/Tor support (system-level) | Not enforced |
| Query history stored | No server-side history; client-side optional | Off |
| NIP-05 resolution leaks interest | Cache NIP-05 lookups locally | On |
| Author search reveals social graph | Already visible via follow lists | N/A |
Stance: Relay-first, pragmatic. Desktop users accept relay visibility for better results. Advanced users can configure search relays or use Tor.
Key Decisions
- Query language is NOT a Nostr standard — it's a client-side UX convention that maps to
Filterfields - Bidirectional sync between text bar and form panel — single source of truth (
SearchQuerydata class) - Kind presets with extensibility — start with core groups, users can toggle individual kinds
- Relay-first execution — NIP-50 search as primary, local cache as supplement
- AI deferred — follow-up feature, will parse natural language →
SearchQuery - Query parser in commons — shared module so Android can adopt later
- Client-side post-filtering for operators relays can't handle (exclusions, reply detection, media detection)
Resolved Questions
- Name resolution — Async + refine. Search immediately with text, resolve
from:namein background, refine results when pubkey resolved. No blocking. - OR queries — Yes in v1.
bitcoin OR lightningsends parallel relay subscriptions, merges results. - Search history — Recent history (last 20) stored locally.
- Saved searches — Yes in v1. Pin/save queries. Future: persist as Nostr events.
- Result caching — Session cache only (in-memory). Cleared on app restart. No disk persistence.
Open Questions
None — all resolved.
Follow-up Features (Out of Scope)
- AI natural language → query parsing (local Ollama / cloud API / NIP-90 DVM)
- Local SQLite FTS5 index for offline search
- NIP-90 DVM search integration
- Search analytics / trending topics
- Collaborative search (shared saved searches via Nostr events)