Browser-based LaTeX editor with live preview, built for CodeSignal. Learners edit .tex files in CodeMirror on the left; latex.js renders the preview on the right.
npm install
npm run dev # Vite dev server on :5173
npm run build # Build to dist/
IS_PRODUCTION=true npm start # Serve dist/ + API on :3000 (PORT to override)config.json is read on every /config request, so changes take effect on reload.
| Key | Type | Description |
|---|---|---|
starterFiles |
{ filename: contents } |
Initial .tex files |
readOnlyFiles |
string[] |
Files the learner can't rename or delete |
activeFile |
string |
File open at startup |
sidebarExpanded |
boolean |
Whether the file panel starts visible |
Filenames must match ^[A-Za-z0-9][A-Za-z0-9 _.-]*\.tex$.
The server keeps session state in memory. There is no persistence and no auth — the container is single-user.
| Method | Path | Purpose |
|---|---|---|
GET |
/config |
Returns config.json. |
GET |
/snapshot, /content |
Current learner state — see schema below. |
POST |
/snapshot |
Frontend pushes state. Not for external callers. |
GET |
/images |
Image metadata (name, url, mime, size). |
GET |
/image/:name |
Image bytes. |
PUT |
/images/:name |
Upload raw bytes with Content-Type: image/*. |
POST |
/images/fetch |
Body { name, url } — server downloads url and stores it. |
DELETE |
/images/:name |
Remove an image. |
Limits: 5 MB per image, 20 images total, 2 MB per JSON body.
{
"files": { "main.tex": "\\documentclass{article}..." },
"activeFile": "main.tex",
"lastRenderResult": {
"ok": true,
"error": null,
"file": "main.tex",
"timestamp": "2026-06-04T12:00:00.000Z"
}
}lastRenderResult is one of:
null— no render result yet for the currentfiles/activeFile. The learner just edited, switched, renamed, created, or deleted a file and the next render hasn't completed. Poll again.{ ok: true, ... }— the file infilerendered successfully.{ ok: false, error, ... }— render failed;erroris the message.
Graders should treat lastRenderResult === null as "retry, render is pending" — never as success or failure.
src/
main.js app bootstrap, state, snapshot sync
editor.js CodeMirror setup
editorToolbar.js formatting buttons above the editor
filetree.js file/image sidebar
imageUpload.js upload modal
modal.js generic modal
outline.js section outline
preprocess.js LaTeX-source rewrites for latex.js compatibility
preview.js latex.js render + pagination
previewControls.js compile / page / zoom toolbar
server.js Express 5 API + static prod server
vite.config.js Vite + dev-mode API shim
config.json Per-lesson starter files & flags
.github/workflows/release.yml builds on push to main and uploads release.tar.gz (containing dist/, server.js, package.json, package-lock.json, config.json, node_modules) as a GitHub release tagged with the commit SHA.