Platform · 2026
StudyVault
A local-first study OS for CFA, LSAT, quant and Excel — retrieval practice, FSRS scheduling, and answers grounded in your own curriculum.
4 Exam domains
Platform
2026
Key decisions
- The default interaction is retrieval practice, producing an answer from memory before anything is revealed, because the act of retrieving is what consolidates.
- Scheduling runs FSRS-4.5 through ts-fsrs, and an optimizer refits the weights on the user's own review history, because the interval should be a consequence of measured retention rather than a fixed calendar.
- Progress reports what you can still retrieve after a delay, so the number goes down when your retention goes down.
- Everything runs on the user's machine — sidecars bind 127.0.0.1 and a build-time gate fails on non-loopback egress — because the ingested curriculum is licensed for candidate use only.
Built against a real exam
The CFA curriculum is large, hierarchical and unforgiving about the gap between having read something and being able to produce it under time pressure. That makes it a good forcing function for a study tool: anything that only creates a feeling of progress gets caught by the exam.
The same forcing function now applies across four exam domains in one window —
CFA with authored content packs per level (src/domains/cfa), quant, Excel, and
LSAT, a full second prep app with its own FastAPI + SQLite backend serving the
question bank, timed sections, drills and blind review. Each domain keeps its own
router and design system; what they share is the study engine underneath.
Retrieval, not review
Re-reading produces fluency, and fluency feels exactly like knowledge while you are looking at the page. StudyVault is built on retrieval practice — the default interaction produces an answer from memory before revealing anything, because the act of retrieving is what consolidates.
Scheduling is spaced and driven by performance. src/lib/scheduler.ts runs
FSRS-4.5 via the ts-fsrs reference implementation, mapping a confidence-graded
answer onto FSRS ratings — a miss becomes Again, correct-with-high-confidence
becomes Easy — and src/lib/fsrsOptimizer.ts refits a principled subset of the
weights by coordinate descent against the user’s own questionResults history,
persisting the personalised parameters so the interval is a consequence of
measured retention, not a fixed calendar. The LSAT sidecar shares the same
scheduling model over /api/srs/params, so a review in either domain schedules
the same way.
On top of the queue sits the Study Director (src/lib/studyDirector.ts): a pure
rankStudyActions over due reviews, weak-topic mastery and the 14-day forecast,
wrapped by buildStudyPlan, which also interleaves a fraction of slots with
non-weakest topics — the desirable-difficulty move that keeps one bad topic from
becoming the whole plan.
Progress that is not a lie
The metric a study app chooses determines the behaviour it produces. Percentage of material covered rewards moving fast and forgetting; StudyVault reports what you can still retrieve after a delay, so the number goes down when your retention goes down. Less pleasant, considerably more useful in the last month before a sitting. The analytics route plots the 14-day review-load forecast and 30-day mastery-over-time straight from the review log — both are derived from what you actually produced, not what you opened.
Generated practice is held to the same bar as authored content. Mock exams are
built per level from a declared topic blueprint, and every generated or imported
question is routed through the LSAT backend’s /api/gen/generation-quality
rubric — trap metadata, distractor quality, single defensible answer — so one
quality gate spans both domains.
The machine underneath
One Vite bundle carries both apps: the host at / and LSAT at /lsat, split
into lazy chunks so neither domain pays for the other’s code, with soft
navigation between them. The Electron 43 shell runs a sandboxed renderer behind
a typed preload bridge and an app://studyvault protocol, and its main-process
supervisor launches three PyInstaller-packaged sidecars — SurrealDB on :8000,
the open-notebook API and worker on :5055 for notebook RAG, and the LSAT backend
on :8100 — with provenance checks, bounded-backoff restarts and an owned-child
watchdog that reaps them if the app disappears.
Host data lives in Dexie / IndexedDB behind a storage abstraction whose
SurrealDB driver is the cutover target. Grounded answers go through
src/lib/localRag.ts — BM25 plus cosine over local chunks, the union of
curriculum and notebook sources, degrading to host-only when the notebook
sidecar is down — and come back with numbered citation chips. All model
inference is local, against LM Studio or Ollama over the OpenAI-compatible API.
The no-cloud invariant is enforced, not promised: scripts/check-no-egress.mjs
statically scans the shipped renderer, Electron host and LSAT backend for
non-loopback network egress outside a documented allowlist and fails the build
on a hit — because the ingested curriculum PDFs are licensed for candidate use
only, and “works on a plane” is a property of the build, not of the marketing.