noesis — the act of understanding; the exercise of the intellect.
A local-first research desk for arXiv papers. It collects paper metadata, downloads the PDFs to your own disk, and gives you a browser-based reader with a table of contents, persistent highlights, sticky notes, and Markdown export — plus a LaTeX editor that compiles to PDF in the same window.
Everything lives on your machine: SQLite for structured data, plain files for the PDFs and exports. Nothing is sent anywhere except to arXiv itself.
HTML + CSS + vanilla JS ─┐
PDF.js ├── FastAPI (single process) ── SQLite + data/pdfs + data/exports
mermaid.js ─┘
Requires Python 3.9 or newer.
Mermaid and KaTeX are bundled in static/vendor/, so diagrams and mathematics
render with no network connection at all. Only pdf.js is still fetched from a
CDN.
python -m venv .venv
source .venv/bin/activate # Windows: .venv\Scripts\activate
pip install -r requirements.txt
python run.pyOpen http://127.0.0.1:8000. That is the homepage, which explains what the
project is for and links to the documentation; press Open the workspace for
the desk itself at /app. Clicking Noesis anywhere returns to the homepage.
| Route | Page |
|---|---|
/ |
Homepage and philosophy |
/docs-guide |
Full documentation |
/app |
The workspace |
The database and folders are created on first run.
Interactive API docs are at /docs.
A project groups everything done for one piece of work — the papers read, the videos watched, the LaTeX written, the diagrams drawn.
The pill in the masthead switches between them:
- All work — everything is visible, and anything new stays unfiled. This is casual mode: open the app, collect a paper, read it, no ceremony.
- A project — the view filters to it, and anything you collect or create while it is selected is filed there automatically.
Nothing is lost by not choosing. Unfiled work can be assigned to a project later from a paper's ⋯ menu, and deleting a project leaves its contents as unfiled work unless you explicitly ask to purge them.
The choice is stored on the server rather than the browser, so every tab and every mode agrees about what is being worked on.
The masthead switches between them, and the switch beside it toggles the theme.
- Read & explore — collect papers, download PDFs, read, highlight, annotate
- Write — LaTeX projects, compiled to PDF beside the editor
- Draw — a Mermaid workbench with a live canvas and AI diagram generation
- Watch — lectures and talks, with transcripts that join the search index
The hamburger collapses the left rail and the pencil collapses the right panel, in whichever mode you are in.
A dark theme is available from the toggle. It is a brushed-metal grey rather than flat black: panels carry a shallow gradient and a lit top edge. PDF pages stay white, because a document is white and inverting it would misrepresent the source. Your choice is remembered, and a first visit follows the system setting.
Open the Collect tab in the left rail. There are two modes, and both end in the same normalised record, so everything downstream behaves identically.
Keyword search goes through the official arXiv API. You can search everything,
titles or abstracts, narrow by author, category and submission date, choose a sort
order, and set a result cap. Paging happens automatically via start /
max_results, and requests are paced to one every three seconds to stay inside
arXiv's guidance.
Listing URL takes any arXiv listing page and parses every visible entry:
https://arxiv.org/list/math.AP/recent
https://arxiv.org/list/cs.LG/recent
https://arxiv.org/list/stat.ML/recent
From each entry it pulls the arXiv ID, title, author names, abstract page URL and
PDF URL. Titles containing inline TeX come back as readable LaTeX rather than
MathJax debris. Leave Fill in abstracts from the arXiv API checked and each
entry is then enriched by an id_list call, so listing-mode papers end up with
abstracts, dates and affiliations too.
Tick Download PDFs after collecting to fetch everything in one pass, or download papers individually from the library card.
Click any downloaded paper to open it.
- Contents in the left rail shows the PDF's embedded bookmarks; click one to jump to that section. Files with no bookmarks fall back to page shortcuts.
- Pages render lazily as you approach them, so a 90-page paper opens immediately.
- Navigate by page field, arrow keys, or the previous/next buttons. Zoom with the
+/−controls, or use Fit width / Fit page.
Select any text in a page and pick one of five colours. The highlight appears immediately and is saved to SQLite straight away.
Geometry is stored as fractions of the page box rather than pixels, so a highlight lands in exactly the same place after a zoom change, a window resize, a page refresh, a backend restart, or a move to a different screen.
From the Highlights tab you can add or edit a comment, recolour, attach a sticky note, delete, or click through to jump back to the passage in the page.
Notes are written in Markdown and can be attached to three things:
- the whole paper — New sticky note in the Notes tab
- a page — Note on current page in the Paper tab
- a highlight — Add note on any row in the Highlights tab
Notes support a title, a Markdown body and comma-separated tags, and they are editable at any time. They are laid out on a masonry board so long and short notes sit together without gaps.
Export Markdown in the reader bar builds a research note for the open paper.
You get a preview, a clipboard copy, and a file download; a copy is also written
to data/exports/, alongside a JSON bundle of the same content.
# Paper Title
- arXiv ID: 1234.5678
- Authors: A, B, C
- arXiv: https://arxiv.org/abs/1234.5678
- Local PDF: data/pdfs/1234.5678.pdf
## Summary Notes
Overall interpretation here.
## Highlights
### Page 2
> highlighted passage
Comment: important observation
## Sticky Notes
### Numerical idea
This may connect to operator-learning formulations.A chat panel that answers questions about the papers you have collected, using
models running on your own machine. Nothing is sent anywhere: retrieval, image
description and generation all happen over localhost.
Open it from the Chat tab on the right, beside Notes and Highlights.
Install the server:
# macOS
brew install ollama
# Linux
curl -fsSL https://ollama.com/install.sh | sh
# Windows: installer at https://ollama.com/download/windowsStart it and leave it running:
ollama servePull the four models. Together they are about 11 GB:
ollama pull exaone-deep # 4.8 GB reasoning and answering
ollama pull llava # 4.7 GB reads figures out of PDFs
ollama pull nomic-embed-text # 274 MB builds the search index
ollama pull qwen2.5-coder:1.5b # 986 MB inline LaTeX editsEach has a distinct job, and any of them can be swapped for something else in Chat → ⚙ settings:
| Role | Default | What it does |
|---|---|---|
| Chat | exaone-deep |
Answers questions, compares papers, explains passages |
| Vision | llava |
Describes figures found in PDFs, and images you attach |
| Embedding | nomic-embed-text |
Turns text into vectors so passages can be searched |
| LaTeX | qwen2.5-coder:1.5b |
Small and fast; rewrites selected LaTeX in the editor |
If a model is missing, the panel says which one and gives you the exact ollama pull command rather than failing quietly.
Memory. The chat and vision models are roughly 5 GB each. On 8 GB of RAM,
run one at a time, or substitute smaller models (phi3, qwen2.5:3b). Ollama
loads a model on first use and unloads it after a few idle minutes, so having
four installed does not mean four in memory.
Retrieval only sees papers that have been indexed. Download some PDFs, then press Index papers in the chat panel.
For each paper: text is extracted per page, split into overlapping chunks on paragraph boundaries, and embedded. Figures are lifted out, described by the vision model, and those descriptions are embedded too — so asking about a plot finds the plot. The abstract gets its own chunk, because it is often the best match for a broad question.
Indexing a paper takes a few seconds for text, longer with figures, since each image is a separate vision-model call. Everything is stored in the same SQLite file as your notes and highlights.
Type in the box and press Enter (Shift+Enter for a newline). Two toggles control scope:
- Use my library — retrieve passages before answering. Off makes it an ordinary chat with no document grounding.
- This paper only — restrict retrieval to the paper open in the reader.
Every answer carries the passages it used, collapsed under the reply. Click one to open that paper at that page. The assistant is instructed to say when the passages do not contain an answer rather than filling the gap from training data — in research, an admission of not knowing beats a plausible invention.
You can attach images (click the clip, or paste straight into the box) and text files. Attaching an image routes the question to the vision model automatically.
Two different buttons in the compose row, because these are different intentions:
- 📎 Attach — an image, PDF or text file used for this question only. A PDF is parsed server-side, its text handed to the model, and nothing is stored. Use it for a paper someone just sent you, a referee report, a page of notes.
- 📥 Import — adds a PDF to your library permanently. Title, authors and
abstract are read from the file's metadata and first page; it then behaves like
any arXiv paper — readable, highlightable, indexable. Local imports get an ID
beginning
local/so they never collide with real arXiv identifiers.
Images can also be pasted straight into the box.
Reasoning models (exaone-deep, deepseek-r1, qwen3) emit their working in
<thought> tags. Noesis folds that into a collapsible block above the answer —
open it when you want to see the model's process, ignore it otherwise.
Mathematics is typeset with KaTeX, so $\sigma^2$ renders properly instead of
appearing as source. If KaTeX cannot load, the LaTeX source is shown verbatim
rather than mangled. Markdown headings, lists, tables, quotes and code blocks all
render.
Maths is deliberately protected from the markdown pass: underscores and asterisks
are subscripts and multiplication inside $…$, and italicising them would
silently corrupt the formula.
Select any text in a reply and a small bar offers Save as note or Copy. Saving attaches it to the paper open in the reader, so it lands beside your highlights rather than in a separate pile.
Drag the divider between the reading area and the right column. Double-click it to restore the default width. The width is remembered across restarts.
Chat → ⚙ lists everything installed with its size, lets you remove any of
them, and pulls new ones by name with live progress — llama3.2, qwen2.5:7b,
mistral, whatever you want to try. Any model can be assigned to any of the four
roles.
The dropdown beside the scope toggles chooses how the assistant approaches your question. The same passages read very differently depending on what you want from them, and one neutral prompt serves none of these well:
| Role | For |
|---|---|
| Researcher | Synthesis across several papers — agreements, contradictions, gaps |
| Peer reviewer | Adversarial reading: assumptions, evidence, threats to validity |
| Author | Sharpening your own prose, framing and structure |
| Explainer | Careful technical teaching for someone new to the subfield |
| Plain language | No jargon, no equations — for talks, abstracts, explaining to family |
The role layers on top of the grounding rules rather than replacing them, so a peer reviewer still cites real passages and still refuses to invent them. Your choice is remembered.
A small chibi cat shows what the assistant is doing:
| Pose | Meaning |
|---|---|
| Nose in a book, pages turning | reading — retrieving passages, or taking in an attachment |
| Looking up, thought bubbles rising | thinking — the model has the prompt and is working |
| Pen moving, ink appearing | writing — tokens are arriving |
These track the real pipeline rather than a timer. Ollama reports prompt evaluation separately from generation, so the change from thinking to writing is the actual moment the first token appears.
The cat is inline SVG with CSS animation, not a GIF. That keeps the app properly
offline, lets it take the theme's colours, and keeps it sharp at any size.
Animation is suppressed under prefers-reduced-motion; the pose still changes,
so the stage stays readable without movement.
In the writing view, select a fragment and press AI edit (or Ctrl+K). Describe the change — "add a label", "convert this to an align environment", "tighten this sentence" — and the small coder model returns a rewrite.
The suggestion is shown for approval and only replaces your selection when you press the button. A model that silently rewrites your equations is not something to trust blindly.
Worth knowing before you rely on it:
- A 5 GB model is not a frontier model. It will summarise and locate things reliably; it will be less dependable on subtle technical judgement. Check anything that matters against the cited passage — which is why every answer ships with its sources.
- Retrieval is only as good as the chunk. If an argument spans several pages, the retrieved passage may hold only part of it.
- Scanned PDFs yield nothing. Text extraction needs a text layer; image-only scans need OCR first, which is not included.
- Uncensored models. The settings catalogue includes a group of models with their refusal training removed. They are not more accurate for it — an uncensored model invents citations as readily as any other — and everything you generate with one remains your responsibility.
- Small talk. Greetings and acknowledgements skip retrieval entirely. Told to cite passages, a model answering "hello" will otherwise complain that the passages contain no greeting — which is what the first version did.
- Changing the embedding model invalidates the index. Vectors from different models are not comparable, so re-index after switching. Mismatched vectors are skipped rather than silently compared against nonsense.
Switch to Write in the masthead. The layout is editor on the left, typeset PDF on the right, problems behind a tab.
Compilation is real: an actual TeX engine runs on your machine. The app detects what you have and says so in the Projects pane.
| Engine | Install | Packages |
|---|---|---|
| Tectonic (recommended) | cargo install tectonic, or a release binary |
Downloads whatever a document needs, on demand, then caches it |
| TeX Live | apt install texlive-latex-extra latexmk (Debian/Ubuntu), MacTeX, or MiKTeX |
Installed on request via tlmgr from the Problems panel |
Tectonic is the closest thing to Overleaf's behaviour: you never think about packages. It is one binary with no system dependencies. TeX Live works equally well for compiling and is what most people already have.
Without either, projects and files still work — nothing can be typeset.
Overleaf's defining convenience is that you never think about packages: you use
\usepackage{whatever} and it works. Tectonic reproduces that. It ships as one
binary, carries no TeX tree, and downloads exactly the files a document needs
from a curated bundle the first time it needs them, caching them afterwards.
A full TeX Live install is about 7 GB and needs tlmgr babysitting. Tectonic is
roughly 30 MB and needs nothing.
macOS
brew install tectonicLinux (Debian / Ubuntu)
# runtime libraries Tectonic links against
sudo apt install -y libfontconfig1 libgraphite2-3 libharfbuzz0b libicu-dev libssl3
curl --proto '=https' --tlsv1.2 -fsSL https://drop-sh.fullyjustified.net | sh
sudo mv tectonic /usr/local/bin/Linux (Fedora / RHEL)
sudo dnf install -y fontconfig graphite2 harfbuzz libicu openssl
curl --proto '=https' --tlsv1.2 -fsSL https://drop-sh.fullyjustified.net | sh
sudo mv tectonic /usr/local/bin/Windows
winget install TectonicProject.Tectonic
# or: scoop install tectonicFrom source, any platform (needs Rust):
cargo install tectonicWhere the cache lives. Noesis points Tectonic at data/texcache/tectonic
and keeps it there permanently, shared by every project. An earlier version put
this inside each project's build directory, which meant every compile in a new
project re-fetched bundle files it had already downloaded — that is what made
Tectonic seem to hang while latexmk stayed fast. If you have hit that, delete
data/texcache once and let it rebuild cleanly.
Confirm it works. The first run downloads the parts of the bundle your document needs, so it is slower than the ones that follow:
tectonic --version
echo '\documentclass{article}\usepackage{amsmath}\begin{document}$e^{i\pi}+1=0$\end{document}' > t.tex
tectonic t.tex # first run fetches; later runs are fast and offlineRestart Noesis afterwards. The Projects pane will report Tectonic as the engine, and the Problems panel will stop offering to install packages, because nothing will be missing.
One caveat. The initial bundle fetch needs an internet connection, and each
new package a document introduces triggers a small download. After a few
documents the cache (~/.cache/Tectonic) covers most of what you write, and
compilation is fully offline. If you need guaranteed offline from the start,
install TeX Live instead and accept the 7 GB.
New project offers five starting points:
- Article — amsmath, natbib, a working bibliography, theorem environments
- Preprint — title block with affiliations, keywords, reproducibility section
- Slides — Beamer, 16:9
- Reading notes from a paper — seeded with a paper's highlights and notes
- Empty — bare document
The reading-notes template is the reason this lives in the same app: pick a paper
you have already highlighted and you get a document containing its title,
authors, abstract, every highlighted passage as a displayquote grouped by page,
your comments as italics, and your sticky notes as paragraphs. A literature
review that starts from what you actually marked while reading.
Files are listed in the Files tab; the compiled one is marked · main. Add more
with New file — subfolders work, so sections/method.tex pulled in with
\input{sections/method} is fine. Upload adds figures and other binary
assets.
The editor gives you line numbers, LaTeX syntax colouring, two-space indent on
Tab, indent-preserving newlines, and \begin{...} completing its own \end{...}
when you press Enter. Text autosaves about a second after you stop typing;
the state indicator next to the project name tells you where things stand.
Ctrl+S or Ctrl+Enter compiles. So does the Compile button.
New folder creates one (nested paths like figures/experiments work). The
+ beside a folder uploads straight into it; the plain Upload button
targets the project root. Multiple files at once are fine. After an upload you
are told the exact \includegraphics{...} path to use.
Three options sit under the file list:
- Compile without missing figures (on by default) — any
\includegraphics,\inputor\bibliographypointing at a file that is not there yet draws a labelled grey box instead of stopping the build. Float numbering, captions and cross-references are unaffected, because the figure environment still runs; only its contents are substituted. The Problems panel lists exactly what was stood in for, and entries disappear as you supply the real files. Turn it off for a final build, when a missing figure should be an error. - Download the PDF after each build — saves the compiled PDF automatically on every successful compile.
- Heavy document — forces extra passes. The driver normally works out how many runs a document needs, but deeply cross-referenced documents whose floats shift between passes occasionally settle one run late.
Compilation allows up to ten minutes, which is enough for book-length documents with TikZ and large bibliographies. A 25-page test with 40 floats, a table of contents, lists of figures and tables, 120 cross-references and a 40-entry bibliography builds in about three seconds with every reference resolved.
Commands in the toolbar (or Ctrl+/) searches a catalogue of about thirty
LaTeX constructions by intent rather than by name. Searching LaTeX by name only
works when you already know the name — someone who wants "a matrix with square
brackets" does not know to look for bmatrix. Each entry carries the words a
person would actually use, so that search finds it.
Click a result and the snippet is inserted at your caret, with the cursor placed in the first slot. If it needs a package, you are told which.
Errors and warnings are parsed out of the TeX log into a list, each with its file and line. Click one and the editor jumps there with the line selected. The raw engine log is underneath, collapsed.
When a document asks for a package you do not have, the missing files are
collected into their own box with an Install these button. On TeX Live that
resolves each file to its package with tlmgr search and installs it, preferring
a user tree so it does not need root; if that is not possible it shows you the
exact sudo tlmgr install command. Tectonic needs none of this.
Compilation writes a SyncTeX position map recording, for every box TeX set, the source file and line it came from. Both directions are wired up:
- Click anywhere in the compiled PDF and the editor opens that file, scrolls to the line, selects it, and marks it with a band that fades after a moment.
- Move the caret in the editor and the matching region of the PDF is outlined. The preview only scrolls when the marker is off screen, so typing does not yank it around.
Resolution is the smallest box containing the click, which is the most specific
attribution SyncTeX offers. On prose, headings and ordinary lines this lands
exactly. Inside display-math environments it can be a line or two out — clicking
an equation tends to give you \end{equation} rather than \begin{equation},
because that is where TeX attributes the assembled box. Established SyncTeX
viewers behave the same way.
Clicking a region that came from a class or package file says so rather than failing silently, since those files are not part of your project.
PDF opens the compiled file. Zip downloads the whole project including sources, assets and the built PDF — ready to upload to a journal or to Overleaf.
Shell escape is disabled on every compilation path (-no-shell-escape, and
--untrusted for Tectonic). A .tex file can otherwise execute arbitrary shell
commands through \write18, which matters as soon as you paste in a template
from somewhere else.
A Mermaid workbench. Source on the left, live render on the right.
The canvas behaves like a diagram tool. Drag to pan, wheel to zoom about the cursor, Fit to frame the whole diagram, Reset to return to 1:1. The graph paper background makes panning legible.
Mermaid is served from static/vendor/mermaid.min.js inside the project rather
than a CDN. If a network filter or an offline machine blocks jsDelivr — which was
the original cause of the canvas staying blank — nothing breaks now.
AI generation uses your configured chat model — exaone-deep by default.
Reasoning models are handled properly: their deliberation is stripped, whether or
not they remembered to close the <thought> tag, fences are removed, a lead-in
like "Sure! flowchart TD" is trimmed, and trailing commentary after the diagram is
dropped without mistaking a node label that ends in a full stop for prose. If the
model reasons but never produces a diagram, you are told so and the editor is left
untouched rather than wiped.
Describe what you want — "the pipeline from arXiv ingestion through indexing to a cited answer" — and the local chat model writes the Mermaid. It can also modify what is already there rather than starting over, so you can iterate by describing changes. The generated source lands in the editor, so nothing is a black box: correct it by hand whenever the model gets it wrong.
Seven diagram types come with working starters: flowchart, sequence, class, state, ER, mindmap and Gantt.
Export. Three buttons:
- PNG — rasterised at 2× with the theme's paper colour behind it
- PNG · clear — the same at 2×, transparent background, for dropping onto a slide or into a paper
- SVG — vector, for editing elsewhere
Four details make export reliable, each fixing a real failure:
- Mermaid runs with
htmlLabels: false. With labels on, it wraps each one in a<foreignObject>containing HTML — and a canvas that has had aforeignObjectdrawn onto it is marked tainted, sotoBlobthrows "Tainted canvases may not be exported" and nothing is produced. Plain<text>labels export cleanly. - The SVG is passed to the rasteriser as a data URL, not a blob URL. Blob-backed SVG images are treated as cross-origin by some browsers and taint the canvas too.
- The SVG is given explicit width and height, because some browsers refuse to
rasterise one sized only by its
viewBoxand silently produce a blank image. - Every CSS custom property is resolved to a literal colour first — a serialised SVG has no access to the page's stylesheet, so a themed diagram would otherwise export as solid black.
If you had a diagram on screen from before this fix, re-render it once (edit a
character, or press Fit) so it is redrawn without foreignObject.
Diagrams are saved automatically and kept in the same SQLite file as everything else. Changing the theme redraws them, because Mermaid bakes its palette into the rendered SVG rather than reading it from CSS.
Tags and reading status sit on every paper. The small ring beside each card cycles to-read → reading → read in place, because that changes often enough that a dropdown would be friction. Tags, citation and project assignment live behind the ⋯ button.
The filter row narrows by status and tag; clicking a tag on a card filters to it.
The filter box matches titles. Full text searches the actual text of every indexed paper and transcript — and needs no model running, because the text is kept in a SQLite FTS5 index separate from the assistant's vector store.
Quoted phrases work ("nonlinear eigenvalue"), and hyphenated terms behave
sensibly: sonic-point is split into two tokens rather than mashed into one,
which is how the text was stored.
Papers are text-indexed when you index them for chat, or individually from the ⋯ menu.
Every paper can produce a BibTeX entry, in the form arXiv itself recommends
(@article with eprint and archivePrefix). Cite keys are built as
surname-year-keyword, so bahja2026hybrid rather than something opaque.
From a paper's ⋯ menu, choose a LaTeX project and press Add to
bibliography: the entry is appended to references.bib, duplicates are
skipped, and \cite{key} is copied to your clipboard ready to paste. This is
the bridge between the two halves of the app — reading and writing were
previously separate.
Paste a YouTube link or a whole playlist. Nothing downloads until you ask.
Get transcript is the important one. Subtitles are a few tens of kilobytes against hundreds of megabytes of video, and they are the only part the assistant can read. A fetched transcript becomes keyword-searchable immediately, and Index for chat embeds it so the assistant can cite a talk alongside a paper.
Auto-generated captions repeat each line as the next scrolls in; those duplicates are stripped, or a transcript reads as a stutter at twice its true length.
Download video and Audio only fetch the media itself. Audio extraction
needs ffmpeg; without it, video downloads still work but cap at 720p, because
yt-dlp cannot merge YouTube's separate video and audio streams. The interface
says so rather than failing obscurely.
Downloaded files play locally with no network access at all; until then, playback uses YouTube's privacy-preserving embed.
Export in the library produces one Markdown document covering every paper in scope — optionally just a project, or just a tag. That is a literature review's first draft.
Chat → ⚙ → Your data downloads a zip of the whole desk: database, PDFs,
documents, diagrams and transcripts. Restoring replaces the current data and
keeps the old database alongside as library.db.replaced, so a restore from a
damaged archive is recoverable.
run.py start the server
requirements.txt
app/
main.py FastAPI routes, static mount
latex.py engine detection, compilation, log parsing, tlmgr
projects.py LaTeX projects, files, templates, zip export
synctex.py position map parsing for two-way PDF/editor sync
ollama_client.py local model calls: chat, streaming, embeddings, vision
rag.py PDF parsing, chunking, vector store, retrieval
assistant.py conversations, prompting, staged streaming
draft_assets.py placeholder package for missing figures
roles.py assistant roles and the LaTeX command catalogue
library.py projects, tags, full-text search, BibTeX, backup
videos.py yt-dlp ingestion, subtitles, transcripts
migrations.py additive schema upgrades for existing databases
arxiv_client.py Atom API: paging, retries, throttling, normalisation
list_parser.py listing-page parser (handles MathJax titles)
models.py request schemas
database.py SQLite schema and connection handling
storage.py paper upsert, queries, papers.json cache
downloader.py PDF fetch, skip-if-present, force refresh
annotations.py highlight persistence
notes.py sticky note persistence
exporter.py Markdown and JSON export
static/
home.html homepage and philosophy
docs.html documentation
site.css · site.js styles and theme toggle for both pages
app.html the workspace itself
styles.css warm-paper desk plus a dark metallic theme
api.js backend client and shared helpers
pdf-reader.js PDF.js rendering, outline, highlight overlays
notes.js notes board, highlight list, modal
latex.js editor, compile, preview, problems panel
chat.js assistant panel, streaming answers, attachments
rich.js collapsible reasoning, markdown, KaTeX rendering
draw.js Mermaid workbench: canvas, pan/zoom, PNG and SVG export
cat.js the three-pose working indicator
watch.js the Watch view and the project selector
app.js ingestion, library rail, export
data/
library.db SQLite: papers, authors, paper_authors, annotations,
notes, exports, ingestion_runs, latex_projects,
latex_files, latex_compiles, rag_chunks, rag_status,
chat_threads, chat_messages, settings
papers.json normalised metadata cache and export
pdfs/ downloaded PDFs, named {arxiv_id}.pdf
exports/ generated Markdown and JSON
latex/{id}/ one directory per LaTeX project; .build/ holds output
tests/
fixture_listing.html real arXiv listing markup used to check the parser
data/papers.json ships with three entries collected from a
arxiv.org/list/math.AP/recent page, as a sample of the normalised schema.
| Method | Path | Purpose |
|---|---|---|
| POST | /api/search |
keyword/API ingestion |
| POST | /api/ingest-url |
listing-URL ingestion |
| GET | /api/papers |
library, with search and downloaded filters |
| GET | /api/papers/{id} |
one paper |
| POST | /api/papers/{id}/download |
fetch the PDF; {"force": true} to refetch |
| GET | /api/papers/{id}/pdf |
serve the local PDF |
| DELETE | /api/papers/{id} |
remove from the library |
| GET · POST | /api/papers/{id}/annotations |
list · create highlights |
| PATCH · DELETE | /api/annotations/{id} |
edit · delete a highlight |
| GET · POST | /api/papers/{id}/notes |
list · create notes |
| PATCH · DELETE | /api/notes/{id} |
edit · delete a note |
| POST | /api/papers/{id}/export-markdown |
build the Markdown export |
| GET | /api/stats |
library counters |
| GET | /api/latex/environment |
detected engines and templates |
| GET · POST | /api/latex/projects |
list · create projects |
| GET · PATCH · DELETE | /api/latex/projects/{id} |
one project |
| GET · POST · DELETE | /api/latex/projects/{id}/file |
read · write · delete a file |
| POST | /api/latex/projects/{id}/upload |
add a binary asset |
| POST | /api/latex/projects/{id}/compile |
build, with parsed diagnostics |
| GET | /api/latex/projects/{id}/synctex |
position map, both directions |
| GET | /api/assistant/environment |
Ollama status and model roles |
| POST | /api/assistant/ask |
streamed answer with stages and sources |
| POST | /api/assistant/index |
streamed indexing progress |
| POST | /api/assistant/latex-edit |
rewrite a LaTeX fragment |
| POST | /api/assistant/extract |
read an uploaded file without storing it |
| POST | /api/papers/import |
add a local PDF to the library |
| POST | /api/assistant/pull |
download an Ollama model, streamed |
| GET · POST | /api/workspaces |
list · create projects |
| POST | /api/workspaces/active |
switch the working project |
| GET | /api/search |
full-text search, no model needed |
| GET | /api/papers/{id}/bibtex |
BibTeX for a paper |
| POST | /api/papers/{id}/cite/{project} |
add it to a project's bibliography |
| GET · POST | /api/videos |
list · add videos or playlists |
| POST | /api/videos/{id}/subtitles |
fetch and flatten the transcript |
| GET · POST | /api/backup · /api/restore |
whole-desk archive |
| GET | /api/assistant/roles |
available assistant roles |
| GET | /api/latex/commands |
search the LaTeX catalogue |
| GET · POST | /api/diagrams |
list · create diagrams |
| POST | /api/diagrams/generate |
describe a diagram, get Mermaid back |
| GET | /api/latex/projects/{id}/pdf |
the compiled PDF |
| GET | /api/latex/projects/{id}/zip |
sources plus PDF |
| POST | /api/latex/projects/{id}/install-packages |
resolve and install missing packages |
- Duplicates. Papers are keyed on arXiv ID with the version suffix stripped, so re-running the same collection updates rather than duplicates.
- Downloads. Existing files are skipped unless you force a refresh. Responses that are not actually PDFs are rejected rather than written to disk.
- Rate limits. If arXiv starts refusing requests, wait a minute and retry; the client already backs off between attempts.
- Reduced motion. All animation is disabled automatically when your system asks for reduced motion.
- Bitmap fonts. Templates request
T1encoding only whenlmodernis present. Asking forT1without a scalable font makes TeX generate bitmappkfonts, which look wrong in a PDF and can fail outright on minimal installs. - Build caching. latexmk's
.fdb_latexmkdatabase is cleared before each build. It otherwise refuses to retry after a failure, reporting that all targets are up to date.
Overleaf's defining feature is several people editing one document at once. This has no collaboration, no accounts and no server: it is a single-user local workspace. Forward and reverse SyncTeX are implemented, but there is no continuous preview: you compile when you want to, with Ctrl+S.