quintodrome/ui/src/common
Deluan Quintão 54de0dbc52
feat(server): implement FTS5-based full-text search (#5079)
* build: add sqlite_fts5 build tag to enable FTS5 support

* feat: add SearchBackend config option (default: fts)

* feat: add buildFTS5Query for safe FTS5 query preprocessing

* feat: add FTS5 search backend with config toggle, refactor legacy search

- Add searchExprFunc type and getSearchExpr() for backend selection
- Rename fullTextExpr to legacySearchExpr
- Add ftsSearchExpr using FTS5 MATCH subquery
- Update fullTextFilter in sql_restful.go to use configured backend

* feat: add FTS5 migration with virtual tables, triggers, and search_participants

Creates FTS5 virtual tables for media_file, album, and artist with
unicode61 tokenizer and diacritic folding. Adds search_participants
column, populates from JSON, and sets up INSERT/UPDATE/DELETE triggers.

* feat: populate search_participants in PostMapArgs for FTS5 indexing

* test: add FTS5 search integration tests

* fix: exclude FTS5 virtual tables from e2e DB restore

The restoreDB function iterates all tables in sqlite_master and
runs DELETE + INSERT to reset state. FTS5 contentless virtual tables
cannot be directly deleted from. Since triggers handle FTS5 sync
automatically, simply skip tables matching *_fts and *_fts_* patterns.

* build: add compile-time guard for sqlite_fts5 build tag

Same pattern as netgo: compilation fails with a clear error if
the sqlite_fts5 build tag is missing.

* build: add sqlite_fts5 tag to reflex dev server config

* build: extract GO_BUILD_TAGS variable in Makefile to avoid duplication

* fix: strip leading * from FTS5 queries to prevent "unknown special query" error

* feat: auto-append prefix wildcard to FTS5 search tokens for broader matching

Every plain search token now gets a trailing * appended (e.g., "love" becomes
"love*"), so searching for "love" also matches "lovelace", "lovely", etc.
Quoted phrases are preserved as exact matches without wildcards. Results are
ordered alphabetically by name/title, so shorter exact matches naturally
appear first.

* fix: clarify comments about FTS5 operator neutralization

The comments said "strip" but the code lowercases operators to
neutralize them (FTS5 operators are case-sensitive). Updated comments
to accurately describe the behavior.

* fix: use fmt.Sprintf for FTS5 phrase placeholders

The previous encoding used rune('0'+index) which silently breaks with
10+ quoted phrases. Use fmt.Sprintf for arbitrary index support.

* fix: validate and normalize SearchBackend config option

Normalize the value to lowercase and fall back to "fts" with a log
warning for unrecognized values. This prevents silent misconfiguration
from typos like "FTS", "Legacy", or "fts5".

* refactor: improve documentation for build tags and FTS5 requirements

Signed-off-by: Deluan <deluan@navidrome.org>

* refactor: convert FTS5 query and search backend normalization tests to DescribeTable format

Signed-off-by: Deluan <deluan@navidrome.org>

* fix: add sqlite_fts5 build tag to golangci configuration

Signed-off-by: Deluan <deluan@navidrome.org>

* feat: add UISearchDebounceMs configuration option and update related components

Signed-off-by: Deluan <deluan@navidrome.org>

* fix: fall back to legacy search when SearchFullString is enabled

FTS5 is token-based and cannot match substrings within words, so
getSearchExpr now returns legacySearchExpr when SearchFullString
is true, regardless of SearchBackend setting.

* fix: add sqlite_fts5 build tag to CI pipeline and Dockerfile

* fix: add WHEN clauses to FTS5 AFTER UPDATE triggers

Added WHEN clauses to the media_file_fts_au, album_fts_au, and
artist_fts_au triggers so they only fire when FTS-indexed columns
actually change. Previously, every row update (e.g., play count, rating,
starred status) triggered an unnecessary delete+insert cycle in the FTS
shadow tables. The WHEN clauses use IS NOT for NULL-safe comparison of
each indexed column, avoiding FTS index churn for non-indexed updates.

* feat: add SearchBackend configuration option to data and insights components

Signed-off-by: Deluan <deluan@navidrome.org>

* fix: enhance input sanitization for FTS5 by stripping additional punctuation and special characters

Signed-off-by: Deluan <deluan@navidrome.org>

* feat: add search_normalized column for punctuated name search (R.E.M., AC/DC)

Add index-time normalization and query-time single-letter collapsing to
fix FTS5 search for punctuated names. A new search_normalized column
stores concatenated forms of punctuated words (e.g., "R.E.M." → "REM",
"AC/DC" → "ACDC") and is indexed in FTS5 tables. At query time, runs of
consecutive single letters (from dot-stripping) are collapsed into OR
expressions like ("R E M" OR REM*) to match both the original tokens and
the normalized form. This enables searching by "R.E.M.", "REM", "AC/DC",
"ACDC", "A-ha", or "Aha" and finding the correct results.

* refactor: simplify isSingleUnicodeLetter to avoid []rune allocation

Use utf8.DecodeRuneInString to check for a single Unicode letter
instead of converting the entire string to a []rune slice.

* feat: define ftsSearchColumns for flexible FTS5 search column inclusion

Signed-off-by: Deluan <deluan@navidrome.org>

* feat: update collapseSingleLetterRuns to return quoted phrases for abbreviations

Signed-off-by: Deluan <deluan@navidrome.org>

* feat: implement extractPunctuatedWords to handle artist/album names with embedded punctuation

Signed-off-by: Deluan <deluan@navidrome.org>

* feat: implement extractPunctuatedWords to handle artist/album names with embedded punctuation

Signed-off-by: Deluan <deluan@navidrome.org>

* refactor: punctuated word handling to improve processing of artist/album names

Signed-off-by: Deluan <deluan@navidrome.org>

* feat: add CJK support for search queries with LIKE filters

Signed-off-by: Deluan <deluan@navidrome.org>

* feat: enhance FTS5 search by adding album version support and CJK handling

Signed-off-by: Deluan <deluan@navidrome.org>

* refactor: search configuration to use structured options

Signed-off-by: Deluan <deluan@navidrome.org>

* feat: enhance search functionality to support punctuation-only queries and update related tests

Signed-off-by: Deluan <deluan@navidrome.org>

---------

Signed-off-by: Deluan <deluan@navidrome.org>
2026-02-21 17:52:42 -05:00
..
AddToPlaylistButton.jsx build(ui): migrate from CRA/Jest to Vite/Vitest (#3311) 2024-09-28 11:54:36 -04:00
ArtistLinkField.jsx fix(ui): update artist link rendering and improve button styles 2025-05-23 17:42:19 -04:00
ArtistLinkField.test.jsx feat(server): custom ArtistJoiner config (#3873) 2025-03-23 10:53:21 -04:00
BatchPlayButton.jsx build(ui): migrate from CRA/Jest to Vite/Vitest (#3311) 2024-09-28 11:54:36 -04:00
BatchShareButton.jsx build(ui): migrate from CRA/Jest to Vite/Vitest (#3311) 2024-09-28 11:54:36 -04:00
BitrateField.jsx build(ui): migrate from CRA/Jest to Vite/Vitest (#3311) 2024-09-28 11:54:36 -04:00
CollapsibleComment.jsx feat(ui) add Save Queue to Playlist (#4110) 2025-05-23 22:04:18 -04:00
ContextMenus.jsx fix(ui): always order album tracks by disc and track number (fixes #3720) (#3975) 2025-04-17 19:23:53 -04:00
DateField.jsx feat: add Rated At field - #4653 (#4660) 2025-11-24 23:18:05 -05:00
DocLink.jsx build(ui): migrate from CRA/Jest to Vite/Vitest (#3311) 2024-09-28 11:54:36 -04:00
DurationField.jsx build(ui): migrate from CRA/Jest to Vite/Vitest (#3311) 2024-09-28 11:54:36 -04:00
formatRange.js build(ui): migrate from CRA/Jest to Vite/Vitest (#3311) 2024-09-28 11:54:36 -04:00
index.js fix(ui): Search focus after clear (#4932) 2026-02-21 14:39:38 -05:00
LibrarySelector.jsx feat: Multi-library support (#4181) 2025-07-18 18:41:12 -04:00
LibrarySelector.test.jsx feat: Multi-library support (#4181) 2025-07-18 18:41:12 -04:00
Linkify.jsx Merge commit from fork 2026-02-03 12:22:57 -05:00
Linkify.test.jsx chore(deps): update all dependencies (#4618) 2025-10-25 17:05:16 -04:00
List.jsx feat(server): implement FTS5-based full-text search (#5079) 2026-02-21 17:52:42 -05:00
LoveButton.jsx fix(ui): UI issues & styling coherence (#4910) 2026-01-20 12:45:33 -05:00
MultiLineTextField.jsx chore: format JSX components 2026-02-03 12:46:39 -05:00
Pagination.jsx build(ui): migrate from CRA/Jest to Vite/Vitest (#3311) 2024-09-28 11:54:36 -04:00
ParticipantsInfo.jsx feat(bfr): Big Refactor: new scanner, lots of new fields and tags, improvements and DB schema changes (#2709) 2025-02-19 20:35:17 -05:00
PathField.jsx feat(ui): hide absolute paths from regular users 2025-03-25 20:05:24 -04:00
PathField.test.jsx feat(ui): hide absolute paths from regular users 2025-03-25 20:05:24 -04:00
playbackActions.js feat: add similar songs functionality in agents, and Instant Mix (song-based) to UI (#4919) 2026-01-25 16:16:43 -05:00
PlayButton.jsx fix(ui): always order album tracks by disc and track number (fixes #3720) (#3975) 2025-04-17 19:23:53 -04:00
playlistUtils.js build(ui): migrate from CRA/Jest to Vite/Vitest (#3311) 2024-09-28 11:54:36 -04:00
playlistUtils.test.js build(ui): migrate from CRA/Jest to Vite/Vitest (#3311) 2024-09-28 11:54:36 -04:00
QualityInfo.jsx build(ui): migrate from CRA/Jest to Vite/Vitest (#3311) 2024-09-28 11:54:36 -04:00
QualityInfo.test.jsx build(ui): migrate from CRA/Jest to Vite/Vitest (#3311) 2024-09-28 11:54:36 -04:00
QuickFilter.jsx fix(ui): fix make dev (#3795) 2025-03-10 14:50:16 -04:00
QuickFilter.test.jsx build(ui): migrate from CRA/Jest to Vite/Vitest (#3311) 2024-09-28 11:54:36 -04:00
RangeField.jsx build(ui): migrate from CRA/Jest to Vite/Vitest (#3311) 2024-09-28 11:54:36 -04:00
RatingField.jsx feat: add Rated At field - #4653 (#4660) 2025-11-24 23:18:05 -05:00
SafeHTML.jsx fix(ui): use div for fragment, check lastfm url for artist page (#4980) 2026-02-04 17:34:26 -05:00
sanitizeFieldRestProps.jsx build(ui): migrate from CRA/Jest to Vite/Vitest (#3311) 2024-09-28 11:54:36 -04:00
SelectLibraryInput.jsx feat: Multi-library support (#4181) 2025-07-18 18:41:12 -04:00
SelectLibraryInput.test.jsx feat: Multi-library support (#4181) 2025-07-18 18:41:12 -04:00
ShuffleAllButton.jsx fix(ui): skip missing files in bulk operations (#3807) 2025-03-11 20:19:46 -04:00
SimpleList.jsx build(ui): migrate from CRA/Jest to Vite/Vitest (#3311) 2024-09-28 11:54:36 -04:00
SizeField.jsx feat(bfr): Big Refactor: new scanner, lots of new fields and tags, improvements and DB schema changes (#2709) 2025-02-19 20:35:17 -05:00
SongBulkActions.jsx build(ui): migrate from CRA/Jest to Vite/Vitest (#3311) 2024-09-28 11:54:36 -04:00
SongContextMenu.jsx fix(ui): disable shuffle for instant mix playback 2026-01-29 17:04:10 -05:00
SongContextMenu.test.jsx feat: add similar songs functionality in agents, and Instant Mix (song-based) to UI (#4919) 2026-01-25 16:16:43 -05:00
SongDatagrid.jsx fix(ui): always order album tracks by disc and track number (fixes #3720) (#3975) 2025-04-17 19:23:53 -04:00
SongInfo.jsx feat: Multi-library support (#4181) 2025-07-18 18:41:12 -04:00
SongSimpleList.jsx build(ui): migrate from CRA/Jest to Vite/Vitest (#3311) 2024-09-28 11:54:36 -04:00
SongTitleField.jsx feat(bfr): Big Refactor: new scanner, lots of new fields and tags, improvements and DB schema changes (#2709) 2025-02-19 20:35:17 -05:00
Title.jsx build(ui): migrate from CRA/Jest to Vite/Vitest (#3311) 2024-09-28 11:54:36 -04:00
ToggleFieldsMenu.jsx build(ui): migrate from CRA/Jest to Vite/Vitest (#3311) 2024-09-28 11:54:36 -04:00
useAlbumsPerPage.jsx build(ui): migrate from CRA/Jest to Vite/Vitest (#3311) 2024-09-28 11:54:36 -04:00
useGetHandleArtistClick.jsx build(ui): migrate from CRA/Jest to Vite/Vitest (#3311) 2024-09-28 11:54:36 -04:00
useInterval.jsx build(ui): migrate from CRA/Jest to Vite/Vitest (#3311) 2024-09-28 11:54:36 -04:00
useLibrarySelection.js feat: Multi-library support (#4181) 2025-07-18 18:41:12 -04:00
useLibrarySelection.test.js feat: Multi-library support (#4181) 2025-07-18 18:41:12 -04:00
useRating.jsx feat(ui): add song Love and Rating functionality to playlist view (#4134) 2025-06-04 20:38:28 -04:00
useRating.test.js feat(ui): add song Love and Rating functionality to playlist view (#4134) 2025-06-04 20:38:28 -04:00
useRefreshOnEvents.jsx feat: Multi-library support (#4181) 2025-07-18 18:41:12 -04:00
useRefreshOnEvents.test.js feat: Multi-library support (#4181) 2025-07-18 18:41:12 -04:00
useResourceRefresh.jsx feat: Multi-library support (#4181) 2025-07-18 18:41:12 -04:00
useResourceRefresh.test.js build(ui): migrate from CRA/Jest to Vite/Vitest (#3311) 2024-09-28 11:54:36 -04:00
useSearchRefocus.js fix(ui): Search focus after clear (#4932) 2026-02-21 14:39:38 -05:00
useSearchRefocus.test.js fix(ui): Search focus after clear (#4932) 2026-02-21 14:39:38 -05:00
useSelectedFields.jsx build(ui): migrate from CRA/Jest to Vite/Vitest (#3311) 2024-09-28 11:54:36 -04:00
useToggleLove.jsx feat(ui): add song Love and Rating functionality to playlist view (#4134) 2025-06-04 20:38:28 -04:00
useToggleLove.test.js feat(ui): add song Love and Rating functionality to playlist view (#4134) 2025-06-04 20:38:28 -04:00
useTraceUpdate.jsx build(ui): migrate from CRA/Jest to Vite/Vitest (#3311) 2024-09-28 11:54:36 -04:00
Writable.jsx build(ui): migrate from CRA/Jest to Vite/Vitest (#3311) 2024-09-28 11:54:36 -04:00