Confer now supports encrypted folders. You can organize your conversations however you like, give each folder custom instructions, and drag them into whatever order makes sense to you.

Just like your Confer messages, your folders are stored as encrypted blobs using keys that only you have access to. That makes ordering the folders slightly more complicated. In a normal application, reordering items is trivial. You store a sort index, update it when things move, and let the database handle the rest. But since Confer folders are encrypted blobs, the database can’t “see” a sort index.

Instead, the sort information is stored inside the encrypted blob. That works for something like folders, because the client can download all the folders, decrypt them, and sort locally without too much overhead.

But this creates a new problem. If the sort order lives inside encrypted blobs, what happens when you reorder a folder? In a traditional system with integer sort indices, moving folder C between folders A and B might require updating the indices of every folder after the insertion point. With encryption, each update means re-encrypting and re-uploading. Reorder one folder, re-encrypt and re-upload twenty.

Fractional indexing

Instead, we can use fractional indexing—a technique that generates sort keys which can always be split.

Instead of assigning folders indices like 1, 2, 3, we assign them opaque strings that sort lexicographically. The first folder might get “a”, the second “b”, the third “c”. If you move the third folder between the first two, we don’t update three indices. We generate a new key between “a” and “b”—something like “aV”—and only update the moved folder.

The main insight is that strings, unlike integers, have infinite room between any two values. Between “a” and “b” there’s “aV”. Between “a” and “aV” there’s “aK”. You can always find a midpoint. No matter how many times you reorder, you only ever update one folder.

In code, it looks like this:

function midpoint(a: string, b: string): string {
  // Find first character where strings differ
  let i = 0;
  while (i < a.length && i < b.length && a[i] === b[i]) i++;

  if (i < a.length) {
    // a has a character at position i
    const charA = a.charCodeAt(i);
    const charB = i < b.length ? b.charCodeAt(i) : 123; // 'z' + 1

    if (charB - charA > 1) {
      // Room between characters—use the midpoint
      return a.slice(0, i) + String.fromCharCode(Math.floor((charA + charB) / 2));
    } else {
      // No room—append a character after a's prefix
      return a.slice(0, i + 1) + 'n'; // 'n' is roughly middle of alphabet
    }
  }

  // a is empty or a prefix of b—append to a
  return a + 'n';
}

// Moving folder C between folders A (sortKey "a") and B (sortKey "b")
const newSortKey = midpoint("a", "b"); // Returns "an"

The midpoint function finds a string that sorts after the first argument and before the second. If there’s room between characters (like between ‘a’ and ‘c’), it picks the middle (‘b’). If not, it extends the string.

The beauty is that each reorder is O(1). One folder gets a new sort key, one encrypted blob gets updated, one request goes to the server. The server does not store any plaintext about what order your folders are in—that information only exists in decrypted form on your device.

Why this matters

This is a trivial detail, and folder organization seems mundane, but it’s a good example of why end-to-end encryption is often more complicated than just “encrypt everything.” The server isn’t just storing bytes—it’s providing a service. When the service requires structure (sorting, searching, filtering), and the structure must remain hidden, you have to think carefully about what leaks.

The answer here – fractional indexing inside encrypted blobs, sorted client-side – is nice because it hides everything. The server learns nothing about your folder names, nothing about their order, nothing about how often you rearrange them. All it sees is a collection of opaque ciphertexts and timestamps.

Some things are worth the complexity.

Try it out!