
Shredding
Everything on this page happens in the client's browser, before a single byte is sent.
Reading files
The app reads .txt and .md as text (Markdown is stripped of code blocks, links and markup) and.pdf with pdf.js, page by page. Files never leave the tab; only fragments do.
Fragments
Text is split on sentence boundaries and packed into fragments of about 150 to 300 characters. A sentence longer than 300 characters is cut on a word boundary; a short tail is merged into its neighbour. The pool refuses any fragment over 400 characters, which keeps a fragment at the size of a thought rather than a paragraph.
Scrub
On by default. Before fragmenting, the app replaces with neutral tokens:
- email addresses →
[email] - EVM and Bitcoin addresses →
[address] - IBAN-shaped strings →
[iban], card-length digit runs →[number] - phone numbers in common international and national formats →
[phone]
The scrub uses patterns, so it catches the obvious; names and free-form secrets are not detected.
Ids, lanes, decoys
Each fragment gets a random 64-bit id. Its lane is drawn at random from 6, then moved if it equals the lane of the fragment before it. About 15 % more fragments are added as decoys: sentences assembled locally from a neutral word stock, with random lanes. The whole list is shuffled. The pool receives { id, text, lane }triples and nothing else.
// in the browser
fragments = split(scrub(text)) // order known here only
lanes = colour(fragments) // lane[i] != lane[i - 1]
wire = shuffle(fragments + decoys) // { id, t, l }
IndexedDB <- { id -> (document, position, text) }The map
The map from id to document and position is written to IndexedDB under this site's origin. Decoy ids are not stored: when the vectors come back, the vectors of decoys have nowhere to go and are dropped. Erasing the library in the app deletes the map and the index.