Asterisk
Work

Tafuta

Open a 2 GB, 27-million-row CSV in 2 ms. Search it in under a second.

What it is
A concept, on invented data
Platform
Desktop (Tauri / Windows, macOS, Linux)
Tafuta showing large.csv: 27,407,065 rows, 'Nairobi' searched in the city column giving 1,521,416 matches, sorted by amount descending. The status bar reads '1,521,416 in view · Sort: 3.34 s'.

The problem

Standard tools — spreadsheet apps, text editors, basic CSV viewers — run out of memory or become unusable with files above a few hundred megabytes. Gigabyte-scale CSVs and logs need something that never loads the whole file.

The idea

Tafuta (Swahili for search) is a Tauri v2 desktop app with a Rust engine that memory-maps the file, builds a parallel row index, and serves a small window of rows to a virtualised React table on demand. The file is never fully in memory.

What it does.

  • Opens files of any size instantly, showing the first rows in 2–3 ms while the full index builds in the background
  • Searches 2 GB of CSV in under 700 ms with plain text or regex, streaming matches as they arrive
  • Handles correct CSV: quoted commas, escaped quotes, embedded newlines, CRLF, BOM, invalid UTF-8
  • Follows growing log files live, with ERROR/FATAL in red and WARN in amber
Tafuta showing large.log: 20,413,939 rows. FATAL and ERROR lines are red, WARN amber, INFO normal, DEBUG and TRACE dimmed. The status bar shows 'Index: 2.41 s'.
Tafuta mid-search on a 2 GB log: 19,834 matches highlighted so far, a progress bar and cancel button active, the table still responsive.
Tafuta opening large.csv: the first rows are already visible on screen while a progress bar at 66% shows the background index still running.

What’s underneath.

The decisions that make it work, most of which no one sees.

  1. The indexer and the parser agree by construction

    A quote only opens a quoted field at the start of a field, matching csv-core's rule exactly. The byte scanner and the field splitter can never disagree on where a row ends, so there is no second pass to reconcile them.

  2. Rust computes highlight ranges as UTF-16 offsets

    get_rows returns each match's start and end as UTF-16 offsets — what JavaScript strings use — so the table never re-implements regex semantics in the frontend. Embedded newlines are shown as ↵ with length-preserving offsets.

  3. Each engine owns its own thread pool

    Sharing rayon's global pool let a cancelled search's chunks queue behind another engine's live work. Moving each engine to its own pool, and checking the cancel flag every 64 rows, brought cancel latency from 1.49 s to under 200 ms.

What it shows

This concept is built from this service. Each is something we can do for you.

Tell us what you’re building.

A 30-minute call is the quickest way to see if we’re the right fit. Or send us a message, whichever suits you.