Feature: Message Search #20
Labels
No labels
area:api
area:core
area:docs
area:infra
area:ux
dependencies
documentation
duplicate
good first issue
help wanted
invalid
question
rust
status:complete
status:partial
status:planned
type:bug
type:design
type:feature
type:infra
type:refactor
type:research
type:ux
wontfix
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
icub3d/decentcom#20
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Migrated from GitHub issue icub3d/decentcom#20
Original Author: @icub3d
Original Date: 2026-04-15T14:15:45Z
Feature: Message Search
Overview
Provide full-text search across messages within a server. Users can search by keyword, with results showing matching messages in context. The feature uses SQLite FTS5 for the default storage backend and is gated behind the
message_searchfeature flag so operators can disable it on resource-constrained servers.Background
The storage doc (
docs/design/storage.md) notes that full-text search requires a search index: FTS5 for SQLite, tsvector for PostgreSQL, or an external service (Meilisearch, Elasticsearch) for large servers. The server model (docs/design/server-model.md) listsmessage_searchas a feature flag (enabled by default) with the note that it "may be expensive for large servers." The initial implementation targets SQLite FTS5 only; PostgreSQL and external search are future work.Requirements
VIEW_CHANNELpermission formessage_searchfeature flag gates the search endpoint; when disabled, returns 403Design
API / Interface Changes
New REST endpoint:
GET /api/v1/searchQuery parameters:
q(required) — Search query string. Passed to FTS5MATCHexpression.channel_id(optional) — Filter to a specific channel.author_id(optional) — Filter by message author.before(optional) — ISO 8601 timestamp, only messages before this time.after(optional) — ISO 8601 timestamp, only messages after this time.limit(optional, default 25, max 50) — Number of results per page.cursor(optional) — Pagination cursor from previous response.Response:
The endpoint checks
VIEW_CHANNELpermission for each result channel, filtering out messages from channels the requester cannot access.New Tauri IPC command:
search_messages(server_id, query, filters)— Calls the search API and returns results to the React frontend.Data Model Changes
New FTS5 virtual table (SQLite):
The
portertokenizer provides stemming (e.g., "running" matches "run"). Theunicode61tokenizer handles Unicode normalization.No new relational tables are needed. The FTS table is a virtual index over the existing
messagestable.Component Changes
Server (
server/):server/src/routes/search.rs— New module: search endpoint with query parsing, FTS5 query execution, permission filtering, paginationserver/src/models/search.rs— New module:SearchResultstruct, FTS query builder that translates user input into safe FTS5 match expressionsserver/src/storage/sqlite/migrations/— Migration to create FTS5 virtual table and triggersserver/src/storage/sqlite/messages.rs— Ensure message insert/update/delete operations work with FTS triggers (no code change if using raw SQL; triggers handle it)Client (
client/):client/src/components/SearchPanel.tsx— New component: search input, filter controls (channel, author, date range), results list with highlighted snippetsclient/src/components/SearchResult.tsx— New component: renders a single search result with message preview, channel name, author, timestamp, and a "Jump to message" linkclient/src/hooks/useSearch.ts— New hook: manages search state, debounced query submission, paginationclient/src/api/search.ts— API client function for the search endpointclient/src/components/ChannelHeader.tsx— Add search icon/button that togglesSearchPanelTask List
Server
server/migrations/009_fts5.sql)server/src/storage/sqlite/search.rs(sanitize_fts_query): handles quoted phrases, strips special chars from bare wordsSearchStoretrait and SQLite impl inserver/src/storage/sqlite/search.rsGET /api/v1/searchendpoint inserver/src/search/handlers.rsRoleStore, included in SQL WHEREchannel_idfilter supportedmessage_searchfeature flag checkClient
client/src/api/search.tsuseSearchhook with debounced query (300ms), pagination state, and loading/error stateSearchPanel.tsxwith search input, filter dropdowns, and results areaSearchResult.tsxwith highlighted snippet, channel badge, author, timestampChannelHeader.tsxTest List
storage/sqlite/search.rs)routes/search_tests.rs)message_searchfeature flag is disabledOpen Questions
rank) or by recency (newest first)? Discord uses recency. Consider offering both via a sort parameter.AND,OR,NOT) in the query syntax, or keep it simple with just keywords and quoted phrases?total_estimatebe an exact count or an approximation? Exact counts on FTS queries can be expensive.tsvector/tsqueryor defer to an external search service?