fractal chat
A group-built document reader with grounded chat: URLs, PDFs, and EPUBs into pgvector; the model chooses when to search.
Fractal Chat is a document reader with grounded chat, built in 19 days at Fractal by four of us. It ingests URLs, PDFs, and EPUBs into Postgres, chunks and embeds them, and chats with a model that retrieves from the uploaded documents; a URL can be one page or a breadth-first crawl capped at 50 same-host pages. Annotations, group sharing, and a Stripe-gated document limit sit on top. By git shortlog: 368 commits split across me (177), Jahnik (104), Nyan Prakash (45), and kudzuweb (42), with Mauria doing styling.
Three decisions I would defend.
pgvector inside Postgres instead of an external vector database.
Chunks
are split at 500 characters with 50 overlap, embedded with
text-embedding-3-small at 512 dimensions, and cosine-searched
in SQL
with the <=> operator. One database holds everything.
Retrieval as a tool, not a pipeline. The model gets a searchDocuments tool whose scope enum covers the current document, mentioned documents, or everything, and decides when to call it. No retrieval happens unless the model asks.
Dual PDF extraction. pdf-parse runs first; when it throws or returns empty text, a pdfjs-dist fallback takes the file. Both imports are lazy, so neither library loads until a PDF arrives.
Annotations anchor as character offsets: start and end integers plus the quoted text and its prefix and suffix. One recursive permission resolver answers every access question, walking direct grants, creator, visibility, parent traversal, and group membership to return admin, write, read, or none.
Numbers, by wc -l over tracked files: 19,126 lines of TypeScript and TSX across 164 files; 18 Drizzle migrations, two pairs of them sharing sequence numbers 0012 and 0016; a 10-document free tier.
What is wrong with it. There are no automated tests — no framework in package.json, no CI. The deployment is dead: fractal-chat.vercel.app returned 404 on 2026-08-09. The prefix and suffix fields were stored to allow re-anchoring annotations after edits, and no one ever wrote the re-anchoring code. Permissions were still being patched on the last day of code work, 2025-10-17, and the Stripe integration says in its own commit message, "added stripe integration using ai, don't yell at me."
The next reader I built, Nexus, started from what this one got wrong.