Scout Search - Google-Style Interface¶
Overview¶
A unified search interface for Scout that provides instant access to messages, rooms, users, shared objects, and campaign data through a single search box.
Design Inspiration¶
┌─────────────────────────────────────────────────────────────────┐
│ 🔍 Search Scout... ⌘K │
├─────────────────────────────────────────────────────────────────┤
│ │
│ RECENT │
│ ├─ 📝 "voter outreach strategy" in #campaign-team │
│ ├─ 👤 Jane Smith │
│ └─ 📊 Segment: High-propensity voters │
│ │
│ SUGGESTED │
│ ├─ 🔍 Search messages... │
│ ├─ 👥 Find users... │
│ ├─ 📁 Browse files... │
│ └─ ❓ Ask Scout AI... │
│ │
└─────────────────────────────────────────────────────────────────┘
Search Categories¶
1. Messages¶
in:messages "voter turnout" # Full-text message search
in:messages from:jane "budget" # Messages from user
in:messages in:#campaign-team # Messages in room
in:messages has:file # Messages with attachments
in:messages after:2024-01-01 # Date filtering
2. Rooms¶
in:rooms campaign # Room name search
in:rooms type:dm # DMs only
in:rooms type:group # Groups only
in:rooms with:jane # Rooms with user
3. Users¶
4. Shared Objects¶
in:files report # File search
in:maps district # Map search
in:queries "score > 80" # Saved queries
in:segments high-priority # Segments
5. Campaign Data (via AI)¶
UI Components¶
Search Bar (Global)¶
Always accessible via ⌘K / Ctrl+K:
<div class="scout-search-overlay">
<div class="scout-search-modal">
<input
type="text"
placeholder="Search Scout..."
class="scout-search-input"
autofocus
/>
<div class="scout-search-results">
<!-- Dynamic results -->
</div>
<div class="scout-search-footer">
<span>↵ to select</span>
<span>↑↓ to navigate</span>
<span>esc to close</span>
</div>
</div>
</div>
Result Types¶
<!-- Message Result -->
<div class="search-result search-result-message">
<div class="result-icon">💬</div>
<div class="result-content">
<div class="result-title">
<span class="result-author">Jane Smith</span>
<span class="result-room">in #campaign-team</span>
</div>
<div class="result-snippet">
...discussing <mark>voter outreach</mark> strategy for Q2...
</div>
<div class="result-meta">Jan 12, 2024</div>
</div>
</div>
<!-- User Result -->
<div class="search-result search-result-user">
<div class="result-icon">
<img src="/avatar/jane" class="avatar-sm" />
</div>
<div class="result-content">
<div class="result-title">Jane Smith</div>
<div class="result-meta">Field Director • Online</div>
</div>
<div class="result-actions">
<button>Message</button>
</div>
</div>
<!-- File Result -->
<div class="search-result search-result-file">
<div class="result-icon">📄</div>
<div class="result-content">
<div class="result-title">Q4 Voter Analysis.pdf</div>
<div class="result-meta">Shared by John • 2.4 MB • Dec 15</div>
</div>
</div>
Backend API¶
Search Endpoint¶
POST /api/scout/search
{
"query": "voter outreach",
"categories": ["messages", "files"], // Optional filter
"limit": 20,
"offset": 0,
"filters": {
"room_id": "uuid", // Optional
"user_id": "uuid", // Optional
"date_from": "2024-01-01",
"date_to": "2024-01-31",
"has_object": true
}
}
Response:
{
"results": [
{
"type": "message",
"id": "msg-uuid",
"score": 0.95,
"snippet": "...discussing <em>voter outreach</em>...",
"data": { /* full message */ }
},
{
"type": "file",
"id": "obj-uuid",
"score": 0.82,
"data": { /* file object */ }
}
],
"total": 47,
"categories": {
"messages": 32,
"files": 10,
"users": 3,
"rooms": 2
}
}
Suggestions Endpoint¶
GET /api/scout/search/suggest?q=vot
Response:
{
"suggestions": [
{"text": "voter outreach", "type": "recent"},
{"text": "voters in district 5", "type": "query"},
{"text": "Voter Analysis.pdf", "type": "file"}
]
}
Full-Text Search Implementation¶
Option A: DuckDB FTS (Simple)¶
-- Create FTS index on messages
CREATE INDEX idx_scout_message_fts ON scout_message
USING fts(content);
-- Search query
SELECT * FROM scout_message
WHERE content MATCH 'voter outreach'
ORDER BY fts_score DESC
LIMIT 20;
Option B: Dedicated Search (Better)¶
If search becomes heavy, add a search service:
# Using tantivy (Rust) or meilisearch
class ScoutSearchService:
def index_message(self, message: ScoutMessage):
self.index.add_document({
"id": message.id,
"type": "message",
"content": message.content,
"room_id": message.room_id,
"sender_id": message.sender_id,
"created_at": message.created_at,
"object_type": message.object.type if message.object else None
})
def search(self, query: str, filters: dict) -> list[SearchResult]:
return self.index.search(query, filters)
Search Syntax Parser¶
class SearchQueryParser:
"""Parse Google-style search syntax."""
OPERATORS = {
"in:": "category", # in:messages, in:files
"from:": "sender", # from:jane
"to:": "recipient", # to:john
"has:": "has_object", # has:file, has:map
"after:": "date_from",
"before:": "date_to",
"is:": "status", # is:unread, is:pinned
}
def parse(self, query: str) -> SearchQuery:
tokens = self.tokenize(query)
filters = {}
text_parts = []
for token in tokens:
if ":" in token:
op, value = token.split(":", 1)
if op + ":" in self.OPERATORS:
filters[self.OPERATORS[op + ":"]] = value
continue
text_parts.append(token)
return SearchQuery(
text=" ".join(text_parts),
filters=filters
)
# Example
parser = SearchQueryParser()
q = parser.parse('in:messages from:jane "voter outreach"')
# SearchQuery(text="voter outreach", filters={"category": "messages", "sender": "jane"})
Keyboard Navigation¶
| Key | Action |
|---|---|
⌘K / Ctrl+K |
Open search |
Esc |
Close search |
↑ / ↓ |
Navigate results |
Enter |
Select result |
Tab |
Cycle categories |
⌘Enter |
Open in new tab |
Search Result Actions¶
| Result Type | Primary Action | Secondary Actions |
|---|---|---|
| Message | Jump to message | Copy link, Reply |
| User | Open DM | View profile |
| Room | Open room | Room settings |
| File | Preview/Download | Share, Copy link |
| Query | Execute | Edit, Save |
Implementation Phases¶
Phase 1: Basic Search (2 hours)¶
- Search API endpoint
- DuckDB full-text on messages
- Basic UI with keyboard shortcut
- Message results only
Phase 2: Multi-Category (2 hours)¶
- Add rooms, users, files search
- Category tabs/filters
- Result type renderers
Phase 3: Advanced Syntax (1 hour)¶
- Query parser for operators
- Date range filters
- Object type filters
Phase 4: Suggestions (1 hour)¶
- Recent searches
- Typeahead suggestions
- Popular queries
Phase 5: AI Integration (2 hours)¶
-
/scoutqueries from search - Natural language to structured query
- "Ask AI" result type
Database Schema Additions¶
-- Search history for suggestions
CREATE TABLE IF NOT EXISTS scout_search_history (
id VARCHAR PRIMARY KEY,
user_id VARCHAR NOT NULL,
query VARCHAR NOT NULL,
category VARCHAR,
result_count INTEGER,
selected_result_id VARCHAR,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
CREATE INDEX idx_scout_search_user ON scout_search_history(user_id, created_at DESC);
-- Full-text index (if not using external search)
-- DuckDB FTS extension required
INSTALL fts;
LOAD fts;
PRAGMA create_fts_index('scout_message', 'id', 'content');
Related Features¶
- Command Palette:
⌘Kcan also show commands (like VS Code) - Quick Switcher: Fast room switching with
⌘Kthen type room name - AI Chat: Natural language queries via
/scout
Open Questions¶
- Scope: Search across all rooms or respect room membership?
- Privacy: Can admins search all messages?
- Indexing: Real-time or batch indexing?
- External: Include campaign data (contacts, events) in search?