{
"path": "/3mjtzyyvsik2w",
"site": "at://did:plc:7vdlgi2bflelz7mmuxoqjfcr/site.standard.publication/3maigovix422i",
"tags": [],
"$type": "site.standard.document",
"title": "Migrating Rockbox Search to Typesense",
"content": {
"$type": "pub.leaflet.content",
"pages": [
{
"id": "019da5c3-2796-7aa8-b11b-7feb6164e6c3",
"$type": "pub.leaflet.pages.linearDocument",
"blocks": [
{
"$type": "pub.leaflet.pages.linearDocument#block",
"block": {
"$type": "pub.leaflet.blocks.text",
"facets": [
{
"index": {
"byteEnd": 11,
"byteStart": 0
},
"features": [
{
"uri": "https://github.com/tsirysndr/rockbox-zig",
"$type": "pub.leaflet.richtext.facet#link"
}
]
},
{
"index": {
"byteEnd": 124,
"byteStart": 115
},
"features": [
{
"uri": "https://typesense.org",
"$type": "pub.leaflet.richtext.facet#link"
}
]
}
],
"plaintext": "Rockbox Zig's search just got a significant overhaul. I replaced the embedded Tantivy full-text search engine with Typesense, an open-source, self-hosted search server. This post covers what changed, why we made the switch, and how the new system works end-to-end."
}
},
{
"$type": "pub.leaflet.pages.linearDocument#block",
"block": {
"$type": "pub.leaflet.blocks.image",
"image": {
"$type": "blob",
"ref": {
"$link": "bafkreicqzmqpo7tshi64m2yilwa6s2ee467eopfznwavoyapvibutvwfmi"
},
"mimeType": "image/webp",
"size": 273062
},
"aspectRatio": {
"width": 2644,
"height": 1774
}
}
},
{
"$type": "pub.leaflet.pages.linearDocument#block",
"block": {
"$type": "pub.leaflet.blocks.header",
"level": 2,
"plaintext": "The Old Approach: Tantivy"
}
},
{
"$type": "pub.leaflet.pages.linearDocument#block",
"block": {
"$type": "pub.leaflet.blocks.text",
"facets": [
{
"index": {
"byteEnd": 42,
"byteStart": 35
},
"features": [
{
"uri": "https://github.com/quickwit-oss/tantivy",
"$type": "pub.leaflet.richtext.facet#link"
}
]
},
{
"index": {
"byteEnd": 178,
"byteStart": 163
},
"features": [
{
"$type": "pub.leaflet.richtext.facet#code"
}
]
}
],
"plaintext": "The previous search was powered by Tantivy, a Rust full-text search library that runs embedded inside the Rockbox process. The implementation lived in a dedicated /crates/search/ crate and maintained separate on-disk indexes for each entity type:"
}
},
{
"$type": "pub.leaflet.pages.linearDocument#block",
"block": {
"$type": "pub.leaflet.blocks.unorderedList",
"children": [
{
"$type": "pub.leaflet.blocks.unorderedList#listItem",
"content": {
"$type": "pub.leaflet.blocks.text",
"plaintext": "Tracks"
}
},
{
"$type": "pub.leaflet.blocks.unorderedList#listItem",
"content": {
"$type": "pub.leaflet.blocks.text",
"plaintext": "Albums"
}
},
{
"$type": "pub.leaflet.blocks.unorderedList#listItem",
"content": {
"$type": "pub.leaflet.blocks.text",
"plaintext": "Artists"
}
},
{
"$type": "pub.leaflet.blocks.unorderedList#listItem",
"content": {
"$type": "pub.leaflet.blocks.text",
"plaintext": "Liked tracks / liked albums"
}
},
{
"$type": "pub.leaflet.blocks.unorderedList#listItem",
"content": {
"$type": "pub.leaflet.blocks.text",
"plaintext": "Files"
}
}
]
}
},
{
"$type": "pub.leaflet.pages.linearDocument#block",
"block": {
"$type": "pub.leaflet.blocks.text",
"facets": [
{
"index": {
"byteEnd": 74,
"byteStart": 44
},
"features": [
{
"$type": "pub.leaflet.richtext.facet#code"
}
]
},
{
"index": {
"byteEnd": 104,
"byteStart": 91
},
"features": [
{
"$type": "pub.leaflet.richtext.facet#code"
}
]
}
],
"plaintext": "Indexes were stored on the filesystem under ~/.config/rockbox.org/indexes/ using Tantivy's MmapDirectory. Each entity type had its own schema definition, and search queries ran against these local indexes synchronously inside the Rockbox server process."
}
},
{
"$type": "pub.leaflet.pages.linearDocument#block",
"block": {
"$type": "pub.leaflet.blocks.text",
"facets": [
{
"index": {
"byteEnd": 13,
"byteStart": 0
},
"features": [
{
"$type": "pub.leaflet.richtext.facet#bold"
}
]
}
],
"plaintext": "The problems:"
}
},
{
"$type": "pub.leaflet.pages.linearDocument#block",
"block": {
"$type": "pub.leaflet.blocks.unorderedList",
"children": [
{
"$type": "pub.leaflet.blocks.unorderedList#listItem",
"content": {
"$type": "pub.leaflet.blocks.text",
"facets": [
{
"index": {
"byteEnd": 89,
"byteStart": 80
},
"features": [
{
"$type": "pub.leaflet.richtext.facet#code"
}
]
}
],
"plaintext": "Searches took long enough that the macOS client needed a loading spinner and an isLoading state variable just to indicate work was happening."
}
},
{
"$type": "pub.leaflet.blocks.unorderedList#listItem",
"content": {
"$type": "pub.leaflet.blocks.text",
"plaintext": "Tantivy indexes lived inside the process — scaling or offloading that work required non-trivial changes."
}
},
{
"$type": "pub.leaflet.blocks.unorderedList#listItem",
"content": {
"$type": "pub.leaflet.blocks.text",
"plaintext": "Running the full-text indexing and query logic inside the server added overhead during library scans."
}
}
]
}
},
{
"$type": "pub.leaflet.pages.linearDocument#block",
"block": {
"$type": "pub.leaflet.blocks.header",
"level": 2,
"plaintext": "The New Approach: Typesense"
}
},
{
"$type": "pub.leaflet.pages.linearDocument#block",
"block": {
"$type": "pub.leaflet.blocks.text",
"plaintext": "Typesense is a standalone search server written in C++ that exposes a clean REST API. Instead of embedding search logic in the Rockbox process, we now run Typesense as a sidecar process and talk to it over HTTP."
}
},
{
"$type": "pub.leaflet.pages.linearDocument#block",
"block": {
"$type": "pub.leaflet.blocks.header",
"level": 3,
"plaintext": "How Typesense is provisioned"
}
},
{
"$type": "pub.leaflet.pages.linearDocument#block",
"block": {
"$type": "pub.leaflet.blocks.text",
"facets": [
{
"index": {
"byteEnd": 26,
"byteStart": 8
},
"features": [
{
"$type": "pub.leaflet.richtext.facet#code"
}
]
}
],
"plaintext": "The new /crates/typesense/ crate handles setup. On startup, it:"
}
},
{
"$type": "pub.leaflet.pages.linearDocument#block",
"block": {
"$type": "pub.leaflet.blocks.orderedList",
"children": [
{
"$type": "pub.leaflet.blocks.orderedList#listItem",
"content": {
"$type": "pub.leaflet.blocks.text",
"facets": [
{
"index": {
"byteEnd": 31,
"byteStart": 15
},
"features": [
{
"$type": "pub.leaflet.richtext.facet#code"
}
]
},
{
"index": {
"byteEnd": 51,
"byteStart": 46
},
"features": [
{
"$type": "pub.leaflet.richtext.facet#code"
}
]
}
],
"plaintext": "Checks whether typesense-server is already on $PATH."
}
},
{
"$type": "pub.leaflet.blocks.orderedList#listItem",
"content": {
"$type": "pub.leaflet.blocks.text",
"facets": [
{
"index": {
"byteEnd": 145,
"byteStart": 66
},
"features": [
{
"$type": "pub.leaflet.richtext.facet#code"
}
]
},
{
"index": {
"byteEnd": 168,
"byteStart": 164
},
"features": [
{
"$type": "pub.leaflet.richtext.facet#code"
}
]
}
],
"plaintext": "If not, downloads the correct binary for the current OS/arch from https://dl.typesense.org/releases/{version}/typesense-server-{os}-{arch}.tar.gz (default version: 30.1)."
}
},
{
"$type": "pub.leaflet.blocks.orderedList#listItem",
"content": {
"$type": "pub.leaflet.blocks.text",
"facets": [
{
"index": {
"byteEnd": 47,
"byteStart": 15
},
"features": [
{
"$type": "pub.leaflet.richtext.facet#code"
}
]
}
],
"plaintext": "Stores data in ~/.config/rockbox.org/typesense/."
}
},
{
"$type": "pub.leaflet.blocks.orderedList#listItem",
"content": {
"$type": "pub.leaflet.blocks.text",
"facets": [
{
"index": {
"byteEnd": 92,
"byteStart": 53
},
"features": [
{
"$type": "pub.leaflet.richtext.facet#code"
}
]
}
],
"plaintext": "Generates (or reuses) a UUID-based API key stored at ~/.config/rockbox.org/typesense/api-key."
}
},
{
"$type": "pub.leaflet.blocks.orderedList#listItem",
"content": {
"$type": "pub.leaflet.blocks.text",
"facets": [
{
"index": {
"byteEnd": 30,
"byteStart": 26
},
"features": [
{
"$type": "pub.leaflet.richtext.facet#code"
}
]
},
{
"index": {
"byteEnd": 66,
"byteStart": 49
},
"features": [
{
"$type": "pub.leaflet.richtext.facet#code"
}
]
}
],
"plaintext": "Starts the server on port 8109 (configurable via RB_TYPESENSE_PORT)."
}
}
],
"startIndex": 1
}
},
{
"$type": "pub.leaflet.pages.linearDocument#block",
"block": {
"$type": "pub.leaflet.blocks.text",
"facets": [
{
"index": {
"byteEnd": 18,
"byteStart": 12
},
"features": [
{
"$type": "pub.leaflet.richtext.facet#code"
}
]
}
],
"plaintext": "Both macOS (darwin) and Linux are supported. The whole provisioning step is transparent — users don't need to install or configure anything separately."
}
},
{
"$type": "pub.leaflet.pages.linearDocument#block",
"block": {
"$type": "pub.leaflet.blocks.header",
"level": 3,
"plaintext": "Collection schemas"
}
},
{
"$type": "pub.leaflet.pages.linearDocument#block",
"block": {
"$type": "pub.leaflet.blocks.text",
"plaintext": "Three Typesense collections mirror the main entity types:"
}
},
{
"$type": "pub.leaflet.pages.linearDocument#block",
"block": {
"$type": "pub.leaflet.blocks.text",
"facets": [
{
"index": {
"byteEnd": 6,
"byteStart": 0
},
"features": [
{
"$type": "pub.leaflet.richtext.facet#bold"
}
]
}
],
"plaintext": "Tracks"
}
},
{
"$type": "pub.leaflet.pages.linearDocument#block",
"block": {
"$type": "pub.leaflet.blocks.text",
"facets": [
{
"index": {
"byteEnd": 4,
"byteStart": 0
},
"features": [
{
"$type": "pub.leaflet.richtext.facet#code"
}
]
}
],
"plaintext": "path : string, unique document ID "
}
},
{
"$type": "pub.leaflet.pages.linearDocument#block",
"block": {
"$type": "pub.leaflet.blocks.text",
"facets": [
{
"index": {
"byteEnd": 5,
"byteStart": 0
},
"features": [
{
"$type": "pub.leaflet.richtext.facet#code"
}
]
},
{
"index": {
"byteEnd": 13,
"byteStart": 7
},
"features": [
{
"$type": "pub.leaflet.richtext.facet#code"
}
]
},
{
"index": {
"byteEnd": 20,
"byteStart": 15
},
"features": [
{
"$type": "pub.leaflet.richtext.facet#code"
}
]
},
{
"index": {
"byteEnd": 34,
"byteStart": 22
},
"features": [
{
"$type": "pub.leaflet.richtext.facet#code"
}
]
}
],
"plaintext": "title, artist, album, album_artist: string, searchable "
}
},
{
"$type": "pub.leaflet.pages.linearDocument#block",
"block": {
"$type": "pub.leaflet.blocks.text",
"facets": [
{
"index": {
"byteEnd": 5,
"byteStart": 0
},
"features": [
{
"$type": "pub.leaflet.richtext.facet#code"
}
]
},
{
"index": {
"byteEnd": 15,
"byteStart": 7
},
"features": [
{
"$type": "pub.leaflet.richtext.facet#code"
}
]
}
],
"plaintext": "genre, composer: string "
}
},
{
"$type": "pub.leaflet.pages.linearDocument#block",
"block": {
"$type": "pub.leaflet.blocks.text",
"facets": [
{
"index": {
"byteEnd": 7,
"byteStart": 0
},
"features": [
{
"$type": "pub.leaflet.richtext.facet#code"
}
]
},
{
"index": {
"byteEnd": 18,
"byteStart": 9
},
"features": [
{
"$type": "pub.leaflet.richtext.facet#code"
}
]
},
{
"index": {
"byteEnd": 28,
"byteStart": 20
},
"features": [
{
"$type": "pub.leaflet.richtext.facet#code"
}
]
},
{
"index": {
"byteEnd": 36,
"byteStart": 30
},
"features": [
{
"$type": "pub.leaflet.richtext.facet#code"
}
]
}
],
"plaintext": "bitrate, frequency, filesize, length: int "
}
},
{
"$type": "pub.leaflet.pages.linearDocument#block",
"block": {
"$type": "pub.leaflet.blocks.text",
"facets": [
{
"index": {
"byteEnd": 12,
"byteStart": 0
},
"features": [
{
"$type": "pub.leaflet.richtext.facet#code"
}
]
},
{
"index": {
"byteEnd": 25,
"byteStart": 14
},
"features": [
{
"$type": "pub.leaflet.richtext.facet#code"
}
]
},
{
"index": {
"byteEnd": 31,
"byteStart": 27
},
"features": [
{
"$type": "pub.leaflet.richtext.facet#code"
}
]
}
],
"plaintext": "track_number, disc_number, year: int "
}
},
{
"$type": "pub.leaflet.pages.linearDocument#block",
"block": {
"$type": "pub.leaflet.blocks.text",
"facets": [
{
"index": {
"byteEnd": 3,
"byteStart": 0
},
"features": [
{
"$type": "pub.leaflet.richtext.facet#code"
}
]
}
],
"plaintext": "md5: string, content hash "
}
},
{
"$type": "pub.leaflet.pages.linearDocument#block",
"block": {
"$type": "pub.leaflet.blocks.text",
"facets": [
{
"index": {
"byteEnd": 9,
"byteStart": 0
},
"features": [
{
"$type": "pub.leaflet.richtext.facet#code"
}
]
},
{
"index": {
"byteEnd": 20,
"byteStart": 11
},
"features": [
{
"$type": "pub.leaflet.richtext.facet#code"
}
]
},
{
"index": {
"byteEnd": 30,
"byteStart": 22
},
"features": [
{
"$type": "pub.leaflet.richtext.facet#code"
}
]
},
{
"index": {
"byteEnd": 40,
"byteStart": 32
},
"features": [
{
"$type": "pub.leaflet.richtext.facet#code"
}
]
}
],
"plaintext": "album_art, artist_id, album_id, genre_id: string, optional "
}
},
{
"$type": "pub.leaflet.pages.linearDocument#block",
"block": {
"$type": "pub.leaflet.blocks.text",
"plaintext": ""
}
},
{
"$type": "pub.leaflet.pages.linearDocument#block",
"block": {
"$type": "pub.leaflet.blocks.text",
"facets": [
{
"index": {
"byteEnd": 19,
"byteStart": 14
},
"features": [
{
"$type": "pub.leaflet.richtext.facet#code"
}
]
},
{
"index": {
"byteEnd": 40,
"byteStart": 35
},
"features": [
{
"$type": "pub.leaflet.richtext.facet#code"
}
]
},
{
"index": {
"byteEnd": 48,
"byteStart": 42
},
"features": [
{
"$type": "pub.leaflet.richtext.facet#code"
}
]
},
{
"index": {
"byteEnd": 55,
"byteStart": 50
},
"features": [
{
"$type": "pub.leaflet.richtext.facet#code"
}
]
},
{
"index": {
"byteEnd": 61,
"byteStart": 57
},
"features": [
{
"$type": "pub.leaflet.richtext.facet#code"
}
]
},
{
"index": {
"byteEnd": 70,
"byteStart": 64
},
"features": [
{
"$type": "pub.leaflet.richtext.facet#bold"
}
]
}
],
"plaintext": "Default sort: title. Query fields: title, artist, album, path.\n\nAlbums"
}
},
{
"$type": "pub.leaflet.pages.linearDocument#block",
"block": {
"$type": "pub.leaflet.blocks.text",
"facets": [
{
"index": {
"byteEnd": 5,
"byteStart": 0
},
"features": [
{
"$type": "pub.leaflet.richtext.facet#code"
}
]
},
{
"index": {
"byteEnd": 13,
"byteStart": 7
},
"features": [
{
"$type": "pub.leaflet.richtext.facet#code"
}
]
}
],
"plaintext": "title, artist: string, searchable "
}
},
{
"$type": "pub.leaflet.pages.linearDocument#block",
"block": {
"$type": "pub.leaflet.blocks.text",
"facets": [
{
"index": {
"byteEnd": 4,
"byteStart": 0
},
"features": [
{
"$type": "pub.leaflet.richtext.facet#code"
}
]
}
],
"plaintext": "year: int "
}
},
{
"$type": "pub.leaflet.pages.linearDocument#block",
"block": {
"$type": "pub.leaflet.blocks.text",
"facets": [
{
"index": {
"byteEnd": 3,
"byteStart": 0
},
"features": [
{
"$type": "pub.leaflet.richtext.facet#code"
}
]
},
{
"index": {
"byteEnd": 14,
"byteStart": 5
},
"features": [
{
"$type": "pub.leaflet.richtext.facet#code"
}
]
}
],
"plaintext": "md5, artist_id: string "
}
},
{
"$type": "pub.leaflet.pages.linearDocument#block",
"block": {
"$type": "pub.leaflet.blocks.text",
"facets": [
{
"index": {
"byteEnd": 9,
"byteStart": 0
},
"features": [
{
"$type": "pub.leaflet.richtext.facet#code"
}
]
},
{
"index": {
"byteEnd": 16,
"byteStart": 11
},
"features": [
{
"$type": "pub.leaflet.richtext.facet#code"
}
]
}
],
"plaintext": "album_art, label: string, optional "
}
},
{
"$type": "pub.leaflet.pages.linearDocument#block",
"block": {
"$type": "pub.leaflet.blocks.text",
"plaintext": ""
}
},
{
"$type": "pub.leaflet.pages.linearDocument#block",
"block": {
"$type": "pub.leaflet.blocks.text",
"facets": [
{
"index": {
"byteEnd": 7,
"byteStart": 0
},
"features": [
{
"$type": "pub.leaflet.richtext.facet#bold"
}
]
}
],
"plaintext": "Artists"
}
},
{
"$type": "pub.leaflet.pages.linearDocument#block",
"block": {
"$type": "pub.leaflet.blocks.text",
"plaintext": "| Field | Type | Notes |"
}
},
{
"$type": "pub.leaflet.pages.linearDocument#block",
"block": {
"$type": "pub.leaflet.blocks.text",
"plaintext": "|---|---|---|"
}
},
{
"$type": "pub.leaflet.pages.linearDocument#block",
"block": {
"$type": "pub.leaflet.blocks.text",
"facets": [
{
"index": {
"byteEnd": 6,
"byteStart": 2
},
"features": [
{
"$type": "pub.leaflet.richtext.facet#code"
}
]
}
],
"plaintext": "| name | string | searchable |"
}
},
{
"$type": "pub.leaflet.pages.linearDocument#block",
"block": {
"$type": "pub.leaflet.blocks.text",
"facets": [
{
"index": {
"byteEnd": 5,
"byteStart": 2
},
"features": [
{
"$type": "pub.leaflet.richtext.facet#code"
}
]
},
{
"index": {
"byteEnd": 12,
"byteStart": 7
},
"features": [
{
"$type": "pub.leaflet.richtext.facet#code"
}
]
}
],
"plaintext": "| bio, image | string | optional |"
}
},
{
"$type": "pub.leaflet.pages.linearDocument#block",
"block": {
"$type": "pub.leaflet.blocks.text",
"plaintext": ""
}
},
{
"$type": "pub.leaflet.pages.linearDocument#block",
"block": {
"$type": "pub.leaflet.blocks.header",
"level": 2,
"plaintext": "Data Flow"
}
},
{
"$type": "pub.leaflet.pages.linearDocument#block",
"block": {
"$type": "pub.leaflet.blocks.header",
"level": 3,
"plaintext": "Library scan and indexing"
}
},
{
"$type": "pub.leaflet.pages.linearDocument#block",
"block": {
"$type": "pub.leaflet.blocks.text",
"plaintext": "When Rockbox scans your music library it:"
}
},
{
"$type": "pub.leaflet.pages.linearDocument#block",
"block": {
"$type": "pub.leaflet.blocks.orderedList",
"children": [
{
"$type": "pub.leaflet.blocks.orderedList#listItem",
"content": {
"$type": "pub.leaflet.blocks.text",
"plaintext": "Walks the music directory recursively looking for supported audio formats (mp3, flac, ogg, m4a, aac, wav, wv, mpc, aiff, opus, ape, wma, and more)."
}
},
{
"$type": "pub.leaflet.blocks.orderedList#listItem",
"content": {
"$type": "pub.leaflet.blocks.text",
"facets": [
{
"index": {
"byteEnd": 49,
"byteStart": 38
},
"features": [
{
"$type": "pub.leaflet.richtext.facet#code"
}
]
}
],
"plaintext": "Extracts metadata and album art using rockbox_sys FFI calls, running up to 8 files concurrently."
}
},
{
"$type": "pub.leaflet.blocks.orderedList#listItem",
"content": {
"$type": "pub.leaflet.blocks.text",
"plaintext": "Persists everything to a local SQLite database."
}
},
{
"$type": "pub.leaflet.blocks.orderedList#listItem",
"content": {
"$type": "pub.leaflet.blocks.text",
"facets": [
{
"index": {
"byteEnd": 110,
"byteStart": 105
},
"features": [
{
"$type": "pub.leaflet.richtext.facet#bold"
}
]
}
],
"plaintext": "Fetches all tracks, albums, and artists from SQLite, converts them to the Typesense types, serialises to JSONL (JSON Lines), and batch-imports via:"
}
}
],
"startIndex": 1
}
},
{
"$type": "pub.leaflet.pages.linearDocument#block",
"block": {
"$type": "pub.leaflet.blocks.text",
"plaintext": ""
}
},
{
"$type": "pub.leaflet.pages.linearDocument#block",
"block": {
"$type": "pub.leaflet.blocks.code",
"language": "plaintext",
"plaintext": "\nPOST /collections/{collection}/documents/import?action=upsert\n\nX-TYPESENSE-API-KEY: <key>\n\n"
}
},
{
"$type": "pub.leaflet.pages.linearDocument#block",
"block": {
"$type": "pub.leaflet.blocks.text",
"facets": [
{
"index": {
"byteEnd": 10,
"byteStart": 4
},
"features": [
{
"$type": "pub.leaflet.richtext.facet#code"
}
]
}
],
"plaintext": "The upsert action means re-scanning the library is idempotent — existing documents are updated in place rather than duplicated."
}
},
{
"$type": "pub.leaflet.pages.linearDocument#block",
"block": {
"$type": "pub.leaflet.blocks.text",
"plaintext": ""
}
},
{
"$type": "pub.leaflet.pages.linearDocument#block",
"block": {
"$type": "pub.leaflet.blocks.header",
"level": 3,
"plaintext": "Search queries"
}
},
{
"$type": "pub.leaflet.pages.linearDocument#block",
"block": {
"$type": "pub.leaflet.blocks.text",
"plaintext": "The HTTP search handler sits at:"
}
},
{
"$type": "pub.leaflet.pages.linearDocument#block",
"block": {
"$type": "pub.leaflet.blocks.code",
"language": "plaintext",
"plaintext": "\nGET /search?q={query}\n\n"
}
},
{
"$type": "pub.leaflet.pages.linearDocument#block",
"block": {
"$type": "pub.leaflet.blocks.text",
"facets": [
{
"index": {
"byteEnd": 108,
"byteStart": 76
},
"features": [
{
"$type": "pub.leaflet.richtext.facet#code"
}
]
},
{
"index": {
"byteEnd": 148,
"byteStart": 121
},
"features": [
{
"$type": "pub.leaflet.richtext.facet#code"
}
]
},
{
"index": {
"byteEnd": 178,
"byteStart": 165
},
"features": [
{
"$type": "pub.leaflet.richtext.facet#code"
}
]
}
],
"plaintext": "It fans out to three parallel Typesense requests, one per collection, using query_by=title,artist,album,path for tracks, query_by=title,artist,label for albums, and query_by=name for artists. The responses are merged and returned as a single JSON payload:"
}
},
{
"$type": "pub.leaflet.pages.linearDocument#block",
"block": {
"$type": "pub.leaflet.blocks.code",
"language": "json",
"plaintext": "\n{\n \"tracks\": [...],\n \"albums\": [...],\n \"artists\": [...]\n}\n\n"
}
},
{
"$type": "pub.leaflet.pages.linearDocument#block",
"block": {
"$type": "pub.leaflet.blocks.text",
"facets": [
{
"index": {
"byteEnd": 74,
"byteStart": 61
},
"features": [
{
"$type": "pub.leaflet.richtext.facet#code"
}
]
},
{
"index": {
"byteEnd": 91,
"byteStart": 85
},
"features": [
{
"$type": "pub.leaflet.richtext.facet#code"
}
]
},
{
"index": {
"byteEnd": 99,
"byteStart": 93
},
"features": [
{
"$type": "pub.leaflet.richtext.facet#code"
}
]
},
{
"index": {
"byteEnd": 108,
"byteStart": 101
},
"features": [
{
"$type": "pub.leaflet.richtext.facet#code"
}
]
},
{
"index": {
"byteEnd": 122,
"byteStart": 110
},
"features": [
{
"$type": "pub.leaflet.richtext.facet#code"
}
]
},
{
"index": {
"byteEnd": 140,
"byteStart": 128
},
"features": [
{
"$type": "pub.leaflet.richtext.facet#code"
}
]
}
],
"plaintext": "The same data is also available through the GraphQL API as a SearchResults type with tracks, albums, artists, liked_tracks, and liked_albums fields."
}
},
{
"$type": "pub.leaflet.pages.linearDocument#block",
"block": {
"$type": "pub.leaflet.blocks.text",
"plaintext": ""
}
},
{
"$type": "pub.leaflet.pages.linearDocument#block",
"block": {
"$type": "pub.leaflet.blocks.header",
"level": 2,
"plaintext": "What Changed in the Client"
}
},
{
"$type": "pub.leaflet.pages.linearDocument#block",
"block": {
"$type": "pub.leaflet.blocks.text",
"facets": [
{
"index": {
"byteEnd": 71,
"byteStart": 65
},
"features": [
{
"$type": "pub.leaflet.richtext.facet#bold"
}
]
}
],
"plaintext": "The most visible sign of the improvement is what we were able to remove from the macOS app. With Tantivy, searches were slow enough to require explicit loading UI:"
}
},
{
"$type": "pub.leaflet.pages.linearDocument#block",
"block": {
"$type": "pub.leaflet.blocks.code",
"language": "swift",
"plaintext": "\n// Before\n\n@Published var isLoading: Bool = false\n\n"
}
},
{
"$type": "pub.leaflet.pages.linearDocument#block",
"block": {
"$type": "pub.leaflet.blocks.text",
"facets": [
{
"index": {
"byteEnd": 113,
"byteStart": 100
},
"features": [
{
"$type": "pub.leaflet.richtext.facet#code"
}
]
}
],
"plaintext": "Typesense returns results fast enough that the loading state and spinner are simply gone. The macOS SearchManager now fires a 300 ms debounced query and the results appear — no spinner, no progress view."
}
},
{
"$type": "pub.leaflet.pages.linearDocument#block",
"block": {
"$type": "pub.leaflet.blocks.horizontalRule"
}
},
{
"$type": "pub.leaflet.pages.linearDocument#block",
"block": {
"$type": "pub.leaflet.blocks.header",
"level": 2,
"plaintext": "Configuration Reference"
}
},
{
"$type": "pub.leaflet.pages.linearDocument#block",
"block": {
"$type": "pub.leaflet.blocks.text",
"facets": [
{
"index": {
"byteEnd": 20,
"byteStart": 0
},
"features": [
{
"$type": "pub.leaflet.richtext.facet#code"
}
]
},
{
"index": {
"byteEnd": 79,
"byteStart": 62
},
"features": [
{
"$type": "pub.leaflet.richtext.facet#code"
}
]
},
{
"index": {
"byteEnd": 85,
"byteStart": 81
},
"features": [
{
"$type": "pub.leaflet.richtext.facet#code"
}
]
},
{
"index": {
"byteEnd": 137,
"byteStart": 117
},
"features": [
{
"$type": "pub.leaflet.richtext.facet#code"
}
]
},
{
"index": {
"byteEnd": 143,
"byteStart": 139
},
"features": [
{
"$type": "pub.leaflet.richtext.facet#code"
}
]
},
{
"index": {
"byteEnd": 202,
"byteStart": 180
},
"features": [
{
"$type": "pub.leaflet.richtext.facet#code"
}
]
},
{
"index": {
"byteEnd": 212,
"byteStart": 211
},
"features": [
{
"$type": "pub.leaflet.richtext.facet#code"
}
]
}
],
"plaintext": "RB_TYPESENSE_API_KEY: auto-generated UUID, Authentication key RB_TYPESENSE_PORT: 8109, Port for the Typesense server RB_TYPESENSE_VERSION: 30.1, Version to download if not in PATH ROCKBOX_UPDATE_LIBRARY: Set to 1 to force a library rescan on startup "
}
},
{
"$type": "pub.leaflet.pages.linearDocument#block",
"block": {
"$type": "pub.leaflet.blocks.text",
"plaintext": "You can also trigger a rescan with index rebuild at any time via the HTTP API:"
}
},
{
"$type": "pub.leaflet.pages.linearDocument#block",
"block": {
"$type": "pub.leaflet.blocks.code",
"language": "plaintext",
"plaintext": "\nPOST /system/scan_library?rebuild_index=true\n\n"
}
},
{
"$type": "pub.leaflet.pages.linearDocument#block",
"block": {
"$type": "pub.leaflet.blocks.horizontalRule"
}
},
{
"$type": "pub.leaflet.pages.linearDocument#block",
"block": {
"$type": "pub.leaflet.blocks.header",
"level": 2,
"plaintext": "Summary"
}
},
{
"$type": "pub.leaflet.pages.linearDocument#block",
"block": {
"$type": "pub.leaflet.blocks.text",
"plaintext": "| | Tantivy (before) | Typesense (after) |"
}
},
{
"$type": "pub.leaflet.pages.linearDocument#block",
"block": {
"$type": "pub.leaflet.blocks.text",
"plaintext": "|---------------|----------------|-------------------------|"
}
},
{
"$type": "pub.leaflet.pages.linearDocument#block",
"block": {
"$type": "pub.leaflet.blocks.text",
"plaintext": "| Deployment | Embedded in process | Sidecar server (auto-provisioned) |"
}
},
{
"$type": "pub.leaflet.pages.linearDocument#block",
"block": {
"$type": "pub.leaflet.blocks.text",
"facets": [
{
"index": {
"byteEnd": 51,
"byteStart": 21
},
"features": [
{
"$type": "pub.leaflet.richtext.facet#code"
}
]
},
{
"index": {
"byteEnd": 86,
"byteStart": 54
},
"features": [
{
"$type": "pub.leaflet.richtext.facet#code"
}
]
}
],
"plaintext": "| Storage | ~/.config/rockbox.org/indexes/ | ~/.config/rockbox.org/typesense/ |"
}
},
{
"$type": "pub.leaflet.pages.linearDocument#block",
"block": {
"$type": "pub.leaflet.blocks.text",
"plaintext": "| Index format | Tantivy segments (mmap) | Typesense native |"
}
},
{
"$type": "pub.leaflet.pages.linearDocument#block",
"block": {
"$type": "pub.leaflet.blocks.text",
"plaintext": "| Import format | Tantivy documents | JSONL batch via REST |"
}
},
{
"$type": "pub.leaflet.pages.linearDocument#block",
"block": {
"$type": "pub.leaflet.blocks.text",
"plaintext": "| Search API | In-process function call | HTTP REST |"
}
},
{
"$type": "pub.leaflet.pages.linearDocument#block",
"block": {
"$type": "pub.leaflet.blocks.text",
"plaintext": "| Client loading UI | Required | Removed |"
}
},
{
"$type": "pub.leaflet.pages.linearDocument#block",
"block": {
"$type": "pub.leaflet.blocks.text",
"plaintext": "| Platform | Rust only | OS binary (macOS + Linux) |"
}
},
{
"$type": "pub.leaflet.pages.linearDocument#block",
"block": {
"$type": "pub.leaflet.blocks.text",
"plaintext": "The migration removes an entire crate from the project, replaces it with a thin HTTP client, and delivers noticeably faster results. Typesense stays self-hosted and open-source, so there are no external dependencies or data leaving your machine."
}
},
{
"$type": "pub.leaflet.pages.linearDocument#block",
"block": {
"$type": "pub.leaflet.blocks.text",
"plaintext": ""
}
},
{
"$type": "pub.leaflet.pages.linearDocument#block",
"block": {
"$type": "pub.leaflet.blocks.horizontalRule"
}
},
{
"$type": "pub.leaflet.pages.linearDocument#block",
"block": {
"$type": "pub.leaflet.blocks.text",
"facets": [
{
"index": {
"byteEnd": 40,
"byteStart": 0
},
"features": [
{
"$type": "pub.leaflet.richtext.facet#italic"
}
]
},
{
"index": {
"byteEnd": 72,
"byteStart": 40
},
"features": [
{
"$type": "pub.leaflet.richtext.facet#italic"
},
{
"uri": "https://github.com/tsirysndr/rockbox-zig",
"$type": "pub.leaflet.richtext.facet#link"
}
]
},
{
"index": {
"byteEnd": 73,
"byteStart": 72
},
"features": [
{
"$type": "pub.leaflet.richtext.facet#italic"
}
]
}
],
"plaintext": "The full implementation is available at github.com/tsirysndr/rockbox-zig."
}
},
{
"$type": "pub.leaflet.pages.linearDocument#block",
"block": {
"$type": "pub.leaflet.blocks.text",
"plaintext": ""
}
}
]
}
]
},
"coverImage": {
"$type": "blob",
"ref": {
"$link": "bafkreicqzmqpo7tshi64m2yilwa6s2ee467eopfznwavoyapvibutvwfmi"
},
"mimeType": "image/webp",
"size": 273062
},
"bskyPostRef": {
"cid": "bafyreifmx6vo5xsslda3pyxizhangwvjzw7klwpwdrxa7dq72rakzusdri",
"uri": "at://did:plc:7vdlgi2bflelz7mmuxoqjfcr/app.bsky.feed.post/3mjtzz5sh6s2m",
"commit": {
"cid": "bafyreide6aupvgrxqpuxjbgaaquxbdb4rxel6hdkm2qy5nlqwcvlzgxv5y",
"rev": "3mjtzz5uxfq2o"
},
"validationStatus": "valid"
},
"description": "",
"publishedAt": "2026-04-19T13:16:36.449Z"
}